Программирование с нуля - это совсем просто! 133) Программирование ролевой игры: Квесты-2
Школа программирования
133) Программирование ролевой игры: Квесты-2
Последний выпуск по ролевой игре был N 127.
Начнем с первой структуры, когда герой получает задание у некоторого мирного персонажа на уничтожение определенного монстра. В такой структуре нам потребуются всего два индекса в массиве Monsters - индекс мирного персонажа, который выдает задание, и индекс монстра, подлежащего уничтожению. За успешное выполнение квеста герой получит золото и опыт. Для этого добавим в новый тип еще два поля - размер премиальных в монетах и пунктах опыта. И, конечно, нам каким-то образом необходимо зафиксировать, что данное задание
заключается именно в уничтожении монстра, а не в доставке предмета, для чего введем поле, обозначающее, собственно, тип задания.
В первом приближении структура квеста получится у нас такой:
type TQType = (qKillTarget);
TQuest = record
QType:TQType;
MainInd, TargetId: Integer;
Money, XP: Integer; end;
TQType - это перечислимый тип, который состоит пока всего из одного значения qKillTarget, определяющего цель квеста как уничтожение монстра с индексом TargetId.
Определим теперь массив квестов (пускай их будет 10 на игровой уровень):
const MaxQuests = 10; var Quests: array[1..MaxQuests] of TQuest;
Автоматическую генерацию квеста у нас будет выполнять процедура GenerateQuest:
{ ----------------- } procedure GenerateQuest(qi, hn: Integer; qt: TQType); begin
end;
Первый ее параметр - это номер заполняемого элемента массива Quests, второй - номер мирного персонажа, выдающего данный квест, а третий - выбранная цель квеста.
Но когда эту процедуру вызывать? Обычно это делается, когда герой встречается с неким персонажем и получает от того задание. Для простоты мы будем считать, что все квесты выполняются всеми партийцами, то есть не существует разделения квестов между конкретными героями - один за всех и все за одного!
Пока на карте у нас присутствуют только монстры. Поэтому в процедуру генерации монстров в пещере необходимо добавить код, расставляющий на карте некое число вспомогательных персонажей, при контакте с которыми герой может получить задание. Пусть на каждой карте будет дополнительно три мирных персонажа (NPC, как их обычно называют). Обозначим это число константой:
MaxNPCNum = 3;
Вот как запишется расширенный код:
{ ----------------- } procedure GenerateMonsters; var i,j,x,y: Integer; label Break2; begin for i := 1 to MaxMonsters-MaxNPCNum do begin while true do for j := 1 to MaxMonsterTypes do if (MonsterTypes[j].Level = CurMap) and (random(6)=0) then begin
Monsters[i] := MonsterTypes[j];
FreeMapPoint(x,y);
Monsters[i].x := x;
Monsters[i].y := y; goto Break2 end;
Break2: end;
for i := MaxMonsters-MaxNPCNum+1 to MaxMonsters do begin
Monsters[i] := MonsterTypes[8];
FreeMapPoint(x,y);
Monsters[i].x := x;
Monsters[i].y := y; end;
end;
Первый цикл расстановки "злых" монстров сокращен на MaxNPCNum единиц, а свободные позиции заполняются вторым циклом - непосредственно таким типом "монстров", как "мирный житель". Для удобства будем их также показывать на глобальной карте, чтобы помнить, куда возвращаться после выполнения квеста. Дополним список значений, заносимых в локальный массив gm процедуры ShowGlobalMap, числом 55, обозначающим мирного жителя:
...
n := IsMonsterOnTile( LOCAL_MAP_WIDTH+x, LOCAL_MAP_HEIGHT+y ); if n > 0 then if Monsters[n].ID in GoodMonsterSet then gm[x,(y+1) div 2] := 55 else gm[x,(y+1) div 2] := 5;
...
Символ мирного жителя пусть совпадет с символом монстра, а вот цвет будет не красным, а зеленым:
case gm[x,y] of
...
55: begin
TextColor(LightGreen);
Write( '#' ); end;
...
Теперь можно реализовать процесс получения героем квеста. Пусть
Пусть это будет происходить при контакте с мирным жителем - если на его месте был бы монстр, то началась бы схватка, а нам надо расширить момент контакта до получения (или окончания) квеста. Для этого внесем изменения в процедуру перемещения героя MoveHero - ведь именно в ней контролируется, не нападает ли персонаж на монстров, среди которых теперь может находиться и мирный житель.
{ ----------------- } procedure MoveHero( dx,dy: Integer ); var m, dam: Integer; begin if not FreeTile( GameMap[CurMap].Cells[
Heroes[CurHero].x+dx,Heroes[CurHero].y+dy].Tile ) then
Exit;
m := IsMonsterOnTile(Heroes[CurHero].x+dx, Heroes[CurHero].y+dy); if m > 0 then begin if Monsters[m].ID in GoodMonsterSet then CallQuest(CurHero, m) else begin
HeroAttack(Heroes[CurHero], m);
MonstersStep; end;
Exit end;
...
В коде, анализирующем наличие на новой клетке монстра, появилась проверка мирного жителя и вызов пока не существующей процедуры CallHelp с параметрами - индексами героя и монстра (точнее, неагрессивного персонажа). В этой процедуре, размещенной в модуле Quest, будет скрыт весь сценарный процесс.
Желательно, чтобы один и тот же мирный персонаж не раздавал слишком много квестов налево и направо, иначе они могут потерять актуальность. Как минимум, введем запрет на выдачу квеста, если данный ранее NPC уже предлагал квест, и тот пока не завершился. Определить это можно по полю MainInd (условимся, что оно равно нулю, если соответствующий элемент массива Quests свободен - то есть в нем на данный момент не хранится действующий квест). В переменной n будем хранить такой свободный элемент (его индекс), который
можно задействовать для создания нового квеста.
Если выбранный "мудрец" еще не задействован в выдаче партийцам заданий, его можно привлечь к такому процессу. Для этого сначала найдем подходящего для уничтожения монстра, а потом сделаем из него оригинальный, разнящийся от других объект. Ведь пока все монстры одного типа в игре неотличимы друг от друга!
{ ----------------- } procedure CallQuest(hn, mn: Integer); var i,n: Integer; begin
n := 0; for i := 1 to MaxQuests do if Quests[i].MainInd = mn then begin
ShowInfo(STR_QIS);
Exit end;
for i := 1 to MaxQuests do if Quests[i].MainInd = 0 then begin
n := i;
break end;
if n = 0 then begin
ShowInfo(STR_QMANY);
Exit end;
GenerateQuest(n,mn,qKillTarget);
end;
Текстовые константы:
STR_QIS = ' Мудрец уже выдавал квест, и он еще не завершен! ' ;
STR_QMANY = ' Вы уже выполняете максимально возможное число
квестов! ' ;
В следующем выпуске приступим к формированию конкретного квеста убийства монстра.
Исходный код текущей версии для Turbo Pascal (всегда проверен и работоспособен, главный файл - main.pas):
В книге описаны десятки технологий, каждой из которых посвящены отдельные книги. Таким образом, купив одну мою книгу, вы существенно сэкономите :) В книге полностью описан язык Delphi (версия 2006, полностью совместимая с Turbo Delphi) для обеих платформ - Win32 и .NET. Охвачены также темы работы с файлами на этих платформах, создания файл-серверных, клиент-серверных, распределенных приложений, веб-программ (Indy, ASP.NET, веб-сервисы). Описаны языки SQL и OCL. Немало глав посвящены истории программирования
и различных технологий. Особое внимание уделено созданию программ с помощью технологии ECO и языка моделирования UML - программы фактически рисуются, и теперь даже для создания корпоративных приложений и их переноса в Интернет не обязательно знать программирование!
Отдельная часть отведена технологиям организации групповой работы, управления требованиями, контроля версий, локализации и тестирования.
Тут подробнее про книгу.
Другие мои книги, которые пока доступны в продаже: