Добавлен , опубликован

Задания (квесты)

Содержание:
Важно с самого начала понимать, что такой сущности как задание в игровом движке корсаров не существует. Вся система крутится вокруг обработки отдельно взятых кейсов в файле quests_reaction.c.
Пусть вас не смущает повсеместное использование в коде слова quest - речь всегда идет именно о конкретном сегменте задания (даже не этапе), воплощенного в отдельно взятом кейсе.
Это создаёт дополнительные трудности. Задание, как таковое, существует только в голове автора. В скриптах оно размазано по разным участкам файлов диалогов персонажей, которые в нём задействованы, а также различным кейсам в quests_reaction.c.
Одна из таких трудностей - чтение заданий третьими лицами (да и самим автором, спустя некоторое время). Куски кода раскиданы по разным файлам, чаще всего, никак между собой не связанным. Вы познакомитесь с этим ближе, когда мы будем разбирать реализацию реального задания из игры.
Полное отсутствие общей структуры кода заданий способствет тому, что авторы размещают фрагменты этого кода где угодно. Достаточно частое явление - части кода задания лежат не в quests_reaction.c, а прямо в файлах диалогов персонажей.
Фундаментом квестовой системы Корсаров являются условия. Практически все функции, задействованные в квестовой системе, крутятся вокруг обработки тех или иных условий.
Автор задания прописывает необходимые условия выполнения/провала для каждого кейса в quests_reaction.c, а обработчики затем проверяют выполнение этих условий. Так происходит до тех пор, пока все они не будут выполнены - тогда данный кейс отправляется на исполнение.
В ходе выполнения кейса, помимо самих квестовых действий, также прописывается следующий кейс в цепочке и его условия. Так происходит переход от одной части задания к другой.
В ходе первых нескольких уроков мы разберём непосредственно техническое воплощение квестовой системы. Как игра воспринимает задание, как обрабатываются условия и что ещё, кроме озвученного, происходит.
Примечание:
Если вы пишете только сюжет заданий, а их воплощением в коде занимаются другие, специально обученные, люди - вероятно, вам стоит пропустить уроки с разбором самой системы и перейти сразу к разбору реализации задания "Сопровождение Торговца".
Там вы получите информацию, необходимую для правильного понимания структуры задания, узнаете на какие сегменты его необходимо делить, как подбирать условия к этим сегментам, и прочую информацию, которая поможет сформировать базовое представление об особенностях реализации заданий в Корсарах. Это позволит быстрее написать квест таким образом, чтобы его не пришлось затем несколько раз переделывать, а можно было сразу передать кодеру на реализацию.

Структура системы обработки заданий

С чего всё начинается? С получения задания, конечно.
Чаще всего мы получаем квест в ходе диалога:
// questName это название кейса в quests_reaction.c
void AddDialogExitQuest(string questName)
{
	string attrName;
	aref ar;

	if( CheckAttribute(pchar,"DialogExitQuests") )
	{
		makearef(ar,pchar.DialogExitQuests);
		attrName = "l" + GetAttributesNum(ar);
	}
	else
	{
		attrName = "l0";
	}
	pchar.DialogExitQuests.(attrName) = questName;
}
Важный момент: своей структуры данных у системы заданий нет. Вся служебная, временная и прочая информация пишется в объект pchar (гланый персонаж).
Функция AddDialogExitQuest() вызывается из диалога. Сама по себе, она никаких заданий не запускает, а лишь добавляет переданное название квеста в список атрибутов "DialogExitQuests". Само название квеста, которое мы передаем, это имя кейса в файле quests_reaction.c, который будет выполнен после выхода из диалога.
Далее эта информация будет подхвачена обработчиком, который запускается по завершению диалога.
void QuestDialogExitProcedure()
{
	int i = GetEventData();             // получаем данные из события
	ref othepchar = GetCharacter(i);    // получаем персонажа из индекса
	aref ar, lref;
	string attrName, Lname;

	// эти функции вообще отключены, видимо какое-то наследие Акеллы
	ExecuteAfterDialogTask(othepchar);
	ExecuteAfterDialogTask(pchar);

    // если есть задачи на исполнение по выходу из диалога
	if( CheckAttribute(pchar,"DialogExitQuests") )
	{
        // получаем ссылку на список квестов
		makearef(ar,pchar.DialogExitQuests);

        // узнаем их количество
		int iMax = GetAttributesNum(ar);

        // перебираем циклом
		for(i=0; i<iMax; i++)
		{
            // получаем текущий экземпляр
			lref = GetAttributeN(ar,i);

            // получаем имя квеста
			attrName = GetAttributeValue(lref);

			// получаем имя атрибута
			Lname = GetAttributeName(lref);

            // если указана функция
			if (CheckAttribute(pchar, "DialogExitQuests." + Lname + ".function"))
			{
                // вызываем соответствующую функцию
				call attrName();
			}
			else
			{
			    // иначе - кейс в реакшене
				CompleteQuestName(attrName, "");
			}

            // снимаем прерывание
			if( CheckAttribute(pchar,"quest."+attrName+".win_condition") )	pchar.quest.(attrName).over = "yes";
		}

        // убираем обработку квестов после диалога
		DeleteAttribute(pchar,"DialogExitQuests");
	}

    // вызываем проверку квестов
	QuestsCheck();
}
Из урока по событиям мы знаем, что функция GetEventData() возвращает переданный в событие аргумент. Но что находится в этом аргументе?
Сам вызов функции QuestDialogExitProcedure() завязан на событие выхода из диалога:
SetEventHandler(EVENT_DIALOG_EXIT,"QuestDialogExitProcedure",0);
Данное событие, как не сложно догадаться, вызывается из функции DialogExit(), при помощи которой мы завершаем диалог. Мы не будем сейчас ее всю рассматривать, нас интересует только момент оповещения слушателей события:
PostEvent(EVENT_DIALOG_EXIT, 1, "l", sti(CharacterRef.index));
Это индекс персонажа, с которым мы вели диалог. Собственно, в следующей строке мы получаем ссылку на этого персонажа по его индексу. Но это всё неактуально, потому что функция, в которую этот персонаж передавался - отключена. Очевидно, какое-то наследие от кода Акеллы.
Нас интересует то, что происходит дальше. А дальше идет проверка наличия атрибута "DialogExitQuests", наличие которого означает наличие задач, которые необходимо выполнить по выходу из диалога.
Получаем необходимые атрибуты и в цикле вызываем для всего списка либо функцию (если таковая указана) в reaction_functions.c, либо соответствующий кейс в quests_reaction.c. Но кейс тоже вызывается не напрямую, а через прокладку CompleteQuestName():
void CompleteQuestName(string sQuestName, string qname)
{
	if( CheckAttribute(&objQuestScene,"list."+sQuestName+".chrIdx") )
	{
		Event("qprocTaskEnd","a",GetCharacter(sti(objQuestScene.list.(sQuestName).chrIdx)));
	}
	else
	{
		QuestComplete(sQuestName, qname);
	}
}
Эта функция запускает обработку квестовых сцен, если таковые указаны. Если нет - вызывает кейс в реакшене. В данном уроке сцены я разбирать не буду, поэтому останавливаться здесь не будем. Вернемся к QuestDialogExitProcedure(). Последней строкой в цикле ставится отметка "yes" в атрибут "over". Это нужно для того, чтобы снять прерывание для этого квеста. Подробнее об этом будет ниже.
После завершения цикла мы убираем атрибут "DialogExitQuests" и вызываем проверку квестов:
bool bQuestCheckProcess = false;		// флаг процесса выполнения проверки квестов
bool bQuestCheckProcessFreeze = false;	// флаг паузы проверки квестов

// проверка состояния квестов
void QuestsCheck()
{
	// выход из ф-ции проверки, если выполняется одно из условий:
	//	- проверка уже в процессе выполнения
	//	- проверка заморожена
	//	- активен диалог
	if(bQuestCheckProcess || bQuestCheckProcessFreeze || dialogRun) return;	// условия запуска проверки

	// устанавливаем флаг, что начат процесс проверки квестов
	bQuestCheckProcess = true;

	aref quests;		// пул квестов
	aref quest;			// проверяемый в данный момент квест
	aref conditions;	// пул условий квеста
	aref condition;		// проверяемое в данный момент условие
	int  nQuestsNum;		// счетчик квестов
	int  nConditionsNum;	// счетчик условий
	int  n,m;				// счетчики циклов
	string sQuestName;		// название квеста
	bool bQuestCompleted;	// флаг выполнения условий
	
	// ссылка на список активных квестов
	makearef(quests,pchar.quest);
	
	// кол-во активных квестов
	nQuestsNum = GetAttributesNum(quests);
	
	// проход по всем активным квестам
	for(n = 0; n < nQuestsNum; n++)
	{
		// дополнительная проверка на каждой итерации
		// если начат диалог, или проверка заморожена - переход к следующей итерации
        if (bQuestCheckProcessFreeze || dialogRun) continue;
        
		// обращаемся к конкретному квесту по списку
        quest = GetAttributeN(quests,n);

		// получаем название текущего квеста
		sQuestName = GetAttributeName(quest);

		// проверка условий выполнения
		if(CheckAttribute(quest,"win_condition"))
		{
			// если никаких условий не задано
			if(quest.win_condition == "no")
			{
				// отправляем в обработчик выполнения квеста
				OnQuestComplete(quest, sQuestName);

				// пересчитываем кол-во активных квестов
				nQuestsNum = GetAttributesNum(quests);

				// вызываем следующую итерацию цикла
				continue;
			}

			// получаем список условий квеста
			makearef(conditions,quest.win_condition);

			// ко-во условий
			nConditionsNum = GetAttributesNum(conditions);

			// если кол-во условий равно нулю
			if(nConditionsNum == 0)
			{
				// повторяем процедуру отсутствия условий
				OnQuestComplete(quest, sQuestName);
				nQuestsNum = GetAttributesNum(quests);
				continue;
			}

			// устанавливаем флаг что все условия выполнены
			bQuestCompleted = true;

			// проходим по всем условиям
			for(m = 0; m < nConditionsNum; m++)
			{
				// обращаемся к текущему условию в списке
				condition = GetAttributeN(conditions,m);

				// вызывваем проверку текущего условия
				if(ProcessCondition(condition) == false) 
				{
					// если условие не выполняется - меняем флаг
					bQuestCompleted = false;

					// и прерываем цикл проверки условий
					break;
				}
			}

			// если все условия выполняются
			if(bQuestCompleted) 
			{
				// отправляем в обработчик выполнения квеста
				OnQuestComplete(quest, sQuestName);

				// пересчитываем кол-во активных квестов
				nQuestsNum = GetAttributesNum(quests);
			}
		}
		
		// проверяем условия провала квеста
		if(CheckAttribute(quest,"fail_condition"))
		{
			// получаем список условий провала
			makearef(conditions,quest.fail_condition);

			// кол-во условий провала
			nConditionsNum = GetAttributesNum(conditions);

			// если кол-во условий провала равно нулю
			// переходим к следующей итерации (следующему квесту)
			if(nConditionsNum == 0) continue;

			// проходим по всем условиям провала
			for(m = 0; m < nConditionsNum; m++)
			{
				// обращаемся к текущему условию в списке
				condition = GetAttributeN(conditions,m);

				// если условие провала выполняется
				if(ProcessCondition(condition) == true) 
				{
					// отправляем в обработчик провала квеста
					OnQuestFailed(quest, sQuestName);

					// пересчитываем кол-во активных квестов
					nQuestsNum = GetAttributesNum(quests);

					// прерываем цикл проверки условий провала
					break;
				}
			}
		}
	}

	// кол-во активных квестов
    nQuestsNum = GetAttributesNum(quests);

	// проходим по всем активным квестам
	for(n = 0; n < nQuestsNum; n++)
	{
		// обращаемся к текущему квесту в списке
		quest = GetAttributeN(quests,n);

		// проверяем атрибут завершения квеста
		if(CheckAttribute(quest,"over") && quest.over=="yes")
		{
			// удаляем выполненный или проваленный квест
			DeleteAttribute(quests,GetAttributeName(quest));

			// отнимаем его от счетчика цикла
			n--;

			// и от кол-ва квестов в списке
			nQuestsNum--;
		}
	}

	// снимаем флаг процесса проверки квестов
	bQuestCheckProcess = false;
}

// обработчик выполнения задания
void OnQuestComplete(aref quest, string sQuestname)
{
	// если нет атрибута "квест завершен"
	// и есть атрибут "условия выполнения"
	if(!CheckAttribute(quest,"over") && CheckAttribute(quest,"win_condition"))
	{
		// если квест НЕ повторяющийся (не генераторный)
		if(!CheckAttribute(quest,"again"))
        {
			// устанавливаем атрибут что квест закончен
			// по которому снимается прерывание
            quest.over = "yes";
        }

		// вызываем кейс выполнения квеста в quests_reaction.c
		QuestComplete(quest.win_condition, sQuestName);
	}
}

// обработчик провала задания
void OnQuestFailed(aref quest, string sQuestName)
{
	// если у квеста есть атрибут условия провала
	if(CheckAttribute(quest,"fail_condition"))
	{
		// устанавливаем атрибут что квест закончен
		quest.over = "yes";

		// вызываем кейс провала квеста в quests_reaction.c
		QuestComplete(quest.fail_condition, sQuestName);
	}
}
И это первая глобальная проблема квестовой системы Корсаров - повсеместная прогонка ВСЕХ квестов по каждому чиху. Более предметно мы об этом поговорим в уроке посвященному доработке и улучшениям данной системы.
Я максимально снабдил комментариями весь код функции, поэтому описываю процесс в кратце.
QuestsCheck(), как вы уже поняли, перебирает циклом все активные квесты. Сначала проверяются условия победы ("win_condition"). Если таковых не задано - происходит вызов соответствующего кейса в quests_reaction.c через дополнительную функцию OnQuestComplete(). Её код также описан выше. Если все условия победы выполняются - то же самое. Если условия победы заданы, но не выполнены - переходим к проверке условий провала ("fail_condition"). Если хоть одно условие провала выполняется - вызываем кейс провала задания (тоже через дополнительную ф-цию OnQuestFailed()).
Теперь о самой проверке условий. Она выполняется функцией ProcessCondition():
// проверка условия
bool ProcessCondition(aref condition)
{
	bool bTmp;
	int i;
	int iNation, iLocation; // индексы нации, локации
	ref tmpRef;
	ref refCharacter;		// персонаж
	string sConditionName;	// тип условия
	string sTmpString;      // группа НПЦ
	string locGroup;        // группа локаторов
	string sLocation;       // локация
	float fx,fy,fz;         // координаты

	// получаем тип условия
	sConditionName = GetAttributeValue(condition);

	// если есть условие по персонажу
	if(CheckAttribute(condition,"character"))
	{
		// Алексусом было наворочено несколько дополнительных проверок
  		// для совместимости с кодом Акеллы
		// где в атрибуте "character" хранили индекс (в массиве персонажей)
		// а не идентификатор (строка), по которому сейчас определяются персонажи

		// пытаемся получить индекс персонажа по идентификатору (имени)
		i = GetCharacterIndex(condition.character);

		// если удалось - значит там идентификатор (строка)
		if (i != -1) condition.characterIdx = i;

		// если нет - значит там изначально был записан индекс (число)
		else condition.characterIdx = sti(condition.character);
		
		// удаляем этот атрибут и далее работаем с индексом
		DeleteAttribute(condition,"character");
	}
	
	// если есть условие по персонажу
	if(CheckAttribute(condition,"characterIdx"))
	{
		// получаем ссылку на этого персонажа
		refCharacter = GetCharacter(sti(condition.characterIdx));
	}
	else
	{
		// иначе - работаем с главным героем
		refCharacter = GetMainCharacter();
	}

	// проверяем условие
	switch(sConditionName)
	{
		// выход на глобальную карту
		case "MapEnter":
			// проверяем, инициализирована ли сущность "worldMap"
    		return IsEntity(worldMap);
    	break;

		// выход из локации
    	case "ExitFromLocation":
			// возвращает TRUE если локация, где находится персонаж, отличается от заданной
    		return refCharacter.location != condition.location;
    	break;

		// вход на локацию
        case "location":
			// если локация, на которой находится персонаж, соответствует заданной
    		if(refCharacter.location==condition.location) 
			{
				// отключаем генерацию наземных энкаунтеров (чтоб не мешали квесту)
				bLandEncountersGen = false;
				// возвращаем TRUE если главный герой жив
				return !CharacterIsDead(refCharacter);
			}
			// иначе - возвращаем FALSE
    		return false;
    	break;

		// ограничение по времени (таймер)
        case "Timer":
			// если вы внимательно посмотрите, как засчитывается выполнение данного условия
			// это может вызвать когнитивный диссонанс
			// дело в том, что таймеры задаются не в fail_condition, а в win_condition
			// и для обработки такого провала регистрируется как бы отдельный квест со своим
			// отдельным кейсом в quests_reaction
			// поэтому пересечение границы времени засчитывается как TRUE

			// проверяем год
    		if( GetDataYear() < sti(condition.date.year) ) return false;
    		if( GetDataYear() > sti(condition.date.year) ) return true;

			// проверяем месяц
    		if( GetDataMonth() < sti(condition.date.month) ) return false;
    		if( GetDataMonth() > sti(condition.date.month) ) return true;

			// проверяем день
    		if( GetDataDay() < sti(condition.date.day) ) return false;
    		if (CheckAttribute(condition, "date.hour") && GetDataDay() <= sti(condition.date.day))  //fix
			{
				// и даже конкретное время суток
				if(GetHour() < stf(condition.date.hour)) return false;
				if(GetHour() >= stf(condition.date.hour)) return true;
			}

			// а это на случай, если что-то пошло мимо наших проверок
    		return true;
    	break;

        // нахождение в определенном локаторе
    	case "locator":
            // проверяем, что персонаж находится в нужной локации
    		if(refCharacter.location == condition.location)
    		{
                // получаем группу локаторов
    			locGroup = condition.locator_group;

                // добавляем в атрибут проверки данную группу локаторов
                // если она туда еще не добавлена
    			if( !CheckAttribute(refCharacter,"Quests.LocatorCheck."+locGroup) )
    			{
                    // для начала, проверяем существует ли указанный персонаж
    				if(IsEntity(refCharacter))
    				{
                        // создаем атрибут
    					refCharacter.Quests.LocatorCheck.(locGroup) = "";

                        // получаем координаты персонажа
    					if( GetCharacterPos(refCharacter,&fx,&fy,&fz) )
    					{
                            // проверяем, находятся ли эти координаты
                            // в указанной группе локаторов
    						if( CheckCurLocator(locGroup,condition.locator, fx,fy,fz) )
                                // если ДА - заносим в этот атрибут целевой локатор
                                // для прохождения проверки условия
    							refCharacter.Quests.LocatorCheck.(locGroup) = condition.locator;
    					}

                        // добавляем детектор по группе локаторов
    					AddCharacterLocatorGroup(refCharacter,locGroup);
    				}
    				else
    				{
                        // если персонажа не существует - выводим ошибку в лог
    					Trace("character "+refCharacter.id+" not entity");

                        // и возвращаем FALSE
    					return false;
    				}
    			}

                // срвниваем локатор персонажа с целевым
    			if(refCharacter.Quests.LocatorCheck.(locGroup)==condition.locator)	return true;
    		}

            // если локация не совпадает - возвращаем FALSE
    		return false;
    	break;

        // смерть персонажа
    	case "NPC_Death":
            // проверяем, мертв ли указанный персонаж
    		return CharacterIsDead(refCharacter);
    	break;

		//тип локации
		case "Location_Type":
            // проверяем инициализирована ли локация
			if (IsEntity(loadedLocation))
			{
                // сравниваем тип локации с целевым
                // и жив ли персонаж
				if (loadedLocation.type == condition.location_type) return !CharacterIsDead(refCharacter);
			}

            // иначе возвращаем FALSE
			return false;
		break;

        // колония принадлежит определенной нации
		case "Nation_City":
            // проверяем инициализирована ли локация
			if (IsEntity(loadedLocation))
			{
                // убеждаемся, что данная локация - это город
				if (loadedLocation.type == "town") 
				{
                    // получаем индекс целевой нации
					iNation = sti(condition.nation);

                    // получаем локацию
					sLocation = refCharacter.location;

                    // получаем индекс локации
					iLocation = FindLocation(sLocation);

                    // проверяем наличие атрибута принадлежности к городу
					if(CheckAttribute(&Locations[iLocation], "fastreload"))
					{
                        // получаем название города
						sLocation = Locations[iLocation].fastreload;

                        // проверяем нацию-владельца города
						if(iNation == sti(Colonies[FindColony(sLocation)].nation)) return true;
					}
				}	
			}

            // иначе возвращаем FALSE
			return false;
		break;

        // наличие товара в трюме
        case "Goods":
            // проверяем, что в трюме есть указанный тип товара
            // в указанном количестве
    		return TestIntValue(GetCargoGoods(refCharacter,sti(condition.goods)),sti(condition.quantity),condition.operation);
    	break;

        // наличие предмета у персонажа
    	case "item":
			// проверяем наличие у персонажа целевого предмета
			return CheckCharacterItem(refCharacter,condition.item);
    	break;

        // убийство в ходе абордажа
        case "Character_Capture":
            // проверяем наличие у персонажа атрибута с типом убийства
            // и тип убийства - убит в ходе абордажа
    		if( CheckAttribute(refCharacter,"Killer.status") && sti(refCharacter.Killer.status)==KILL_BY_ABORDAGE ) return true;

            // иначе возвращаем FALSE
    		return false;
    	break;

        // убийство потоплением
        case "Character_sink":
            // проверяем наличие у персонажа атрибута с типом убийства
            // и тип убийства - не в ходе абордажа
    		if( CheckAttribute(refCharacter,"Killer.status") && sti(refCharacter.Killer.status) != KILL_BY_ABORDAGE ) return true;

            // иначе возвращаем FALSE
            return false;
    	break;

        // локация стоянки корабля
        case "Ship_location":
            // проверяем наличие атрибута припаркованного корабля
            // и что корабль находится в указанной локации
    		if( CheckAttribute(refCharacter,"location.from_sea") && refCharacter.location.from_sea==condition.location ) return true;
    		
            // иначе возвращаем FALSE
            return false;
    	break;

        // уничтожение группы персонажей
    	case "Group_Death":
            // получаем целевую группу
			sTmpString = condition.group;

            // проверяем, что группа мертва
			return Group_isDead(sTmpString);
		break;

        // прибытие к острову
        // вход в акваторию??
		case "ComeToIsland":
            // а это тот случай, когда роль играет просто наличие атрибута
            // никаких полезных данных в него не заносится

            // проверяем наличие атрибута "ComeToIsland"
            // который проставляется при входе в акваторию какого-нибудь острова
			if(CheckAttribute(refCharacter,"ComeToIsland") && refCharacter.ComeToIsland=="1")
			{
                // удаляем атрибут
				DeleteAttribute(refCharacter, "ComeToIsland");

                // и возвращаем TRUE
				return true;
			}

            // иначе возвращаем FALSE
			return false;
		break;

        // выход на боевую карту (море)
		case "EnterToSea":
            // проверяем глобальную переменную, которая отвечает за морсую пену (??)
			if(bSeaActive == true)
			{
                // возвращаем TRUE
				return true;
			}
			
            // иначе возвращаем FALSE
            return false;
		break;

        // выход с боевой карты
		case "ExitFromSea":
            // та же переменная, только в этот раз проверяем что она неактивна
			if (bSeaActive == false)
			{
                // возвращаем TRUE
				return true;
			}
			
            // иначе возвращаем FALSE
			return false;
		break;

        // принадлежность локации определенной нации
		case "nation_location":
            // индекс нации
			iNation = sti(condition.nation);

            // получаем локацию, в которой находится персонаж
			sLocation = refCharacter.location;

            // индекс локации
			iLocation = FindLocation(sLocation);

            // если локация с таким индексом существует
			if(iLocation != -1)
			{
                // проверяем наличие атрибута принадлежности к городу
				if(CheckAttribute(&Locations[iLocation], "fastreload"))
				{
                    // получаем название города
					sLocation = Locations[iLocation].fastreload;

                    // индекс нации-владельца города
					int iCurrentNation = sti(Colonies[FindColony(sLocation)].nation);

                    // сравниваем индексы целевой нации и владельца города
					if(iNation == iCurrentNation)
					{
                        // возвращаем TRUE
						return true;
					}
				}
			}
			
            // иначе возвращаем FALSE
			return false;
		break;

        // захват форта
		case "Fort_capture":
            // проверяем наличие атрибута "FortCapture"
            // который выдается при захвате форта
			if( CheckAttribute(refCharacter,"FortCapture") && refCharacter.FortCapture=="1" )
			{
                // удаляем атрибут
				DeleteAttribute(refCharacter, "FortCapture");

                // возвращаем TRUE
				return true;
			}
			
            // иначе возвращаем FALSE
			return false;
		break;

        // захват корабля
		case "Ship_capture":
            // проверяем наличие атрибута "ShipCapture"
            // который выдается при захвате корабля
			if(CheckAttribute(refCharacter,"ShipCapture") && refCharacter.ShipCapture=="1")
			{
                // удаляем атрибут
				DeleteAttribute(refCharacter, "ShipCapture");

                // возвращаем TRUE
				return true;
			}
			
            // иначе возвращаем FALSE
			return false;
		break;

        // нахождение в координатах
		case "Coordinates":
            // здесь реализованы отдельные проверки глобальной и боевой карты

            // проверяем инициализирована ли сущность "worldMap"
			if(IsEntity(worldMap))
			{
                // сравниваем заданные координаты с фактическим
                // положением корабля на глобальной карте
				if( GetMapCoordDegreeX(makefloat(worldMap.playerShipX)) == sti(condition.coordinate.degreeX) &&
				    GetMapCoordDegreeZ(makefloat(worldMap.playerShipZ)) == sti(condition.coordinate.degreeZ) &&
					GetMapCoordMinutesX(makefloat(worldMap.playerShipX)) == sti(condition.coordinate.minutesX) && 
					GetMapCoordMinutesZ(makefloat(worldMap.playerShipZ)) == sti(condition.coordinate.minutesZ))
				{
                    // если совпадают - возвращаем TRUE
					return true;	
				}
                // иначе - возвращаем FALSE
				else return false;	
			}
			else
			{
                // проверяем переменную моря
                // а также переменную абордажа
				if (bSeaActive && !bAbordageStarted)
				{
                    // проверяем наличие атрибута координат корабля
					if (CheckAttribute(pchar, "Ship.pos.x"))
					{
                        // сравниваем заданные координаты с фактическим
                        // положением корабля на боевой карте
						if( GetSeaCoordDegreeX(makefloat(pchar.Ship.pos.x)) == sti(condition.coordinate.degreeX) &&
							GetSeaCoordDegreeZ(makefloat(pchar.Ship.pos.z)) == sti(condition.coordinate.degreeZ) &&
							GetSeaCoordMinutesX(makefloat(pchar.Ship.pos.x)) == sti(condition.coordinate.minutesX) && 
							GetSeaCoordMinutesZ(makefloat(pchar.Ship.pos.z)) == sti(condition.coordinate.minutesZ))
						{	
                            // если совпадают - возвращаем TRUE
							return true;	
						}
                        // иначе - возвращаем FALSE
						else return false;	
					}
				}
			}
		break;
	}

    // если ни один кейс не совпал - пишем в лог ошибку неизвестного типа условия
	trace("ERROR: unidentified condition type()" + condition);

    // и возвращаем FALSE
	return false;
}
При создании квеста мы задаём некоторые условия (из доступных) его выполнения или провала. Данная функция как раз занимается проверкой этих условий. Если условие выполнено - возвращает TRUE, если нет - FALSE.
Я постарался прокомментировать каждую строчку данной функции, чтобы вы могли изучить какие условия вообще доступны и как устроена их проверка.
И последний этап - это, собственно, выполнение кейса в quests_reaction.c. Я не буду копировать сюда все 12 тысяч строк - это не имеет смысла. Нас интересует только сама функция:
void QuestComplete(string sQuestName, string qname)
{
	// различные переменные, использующиеся в квестах
	ref sld, npchar;
	aref arOldMapPos, arAll, arPass;
	int     iTemp, i, ShipType, Rank;
    float locx, locy, locz, fTemp;
	string  attrName, Model, Blade, Gun, sTemp, sQuestTown, sQuestTitle;
	bool   bOk;
	int iChurchGenBanditsCount;

    // вывод логов
	if (bQuestLogShow)
    {
	    Log_Info("Quest completed : " + sQuestName + "  param = " + qname);
		trace("Quest completed : " + sQuestName + "  param = " + qname + " " + GetQuestBookDataDigit());
	}

	// запуск функции вместо кейса, если таковая указана
	if (CheckAttribute(pchar, "quest." + qname + ".function"))
	{
		string sFunction = pchar.quest.(qname).function;
		call sFunction(qname);
		return;
	}

	switch(sQuestName)
	{
        // здесь пошли различные кейсы заданий
        case "Rand_Smuggling":
			pchar.quest.KillSmugglers_after.over = "yes";  
			RemoveSmugglersFromShore();
		break;

        // ...
    }
}
Ничего хитроумного тут нет. Есть настроенный вывод логов, что поможет вам в тестировании своих квестов.
Есть оставшийся от предыдущих аддонов вызов функции вместо кейса, хотя здесь это реализовано еще на этапе диалога, когда вместо AddDialogExitQuest() вызывается AddDialogExitQuestFunction(). Тот же костыль, только в профиль. А далее, собственно, идут все наши кейсы.
В реализации, которую я сейчас ковыряю, функции вынесены в отдельный файл reaction_functions.c, что сокращает кол-во строк в quests_reaction.c с 12 до 8 тысяч. Но глобально проблему это не решает - держать десятки тысяч строк в функции, которая вызывается повсеместно - это вторая большая боль данной системы.

Содержание
`
ОЖИДАНИЕ РЕКЛАМЫ...