Для демонстрации возможностей магической системы создадим два заклинания. Первое, назовем его "Огненный шторм", будет наносить повреждения от 1 до 4 единиц каждому монстру, находящемуся на любой соседней с героем клеток, а второе - "Самоисцеление", позволит быстро восстановить собственное здоровье. За счет маны, конечно.
По какому принципу будут вызываться заклинания? В крупных ролевых проектах список доступных герою заклинаний формируется постепенно. Например, для этого надо пройти обучение в школе или у какого-нибудь мастера, учителя. Существует также концепция "метания заклинаний", когда у героя имеется некий универсальный навык "метания" - выполнения любого заклинания, и если он срабатывает, персонаж уже выбирает нужное из списка выученных на данный момент.
Мы воспользуемся вторым подходом. Добавим в список навыков новый skillThrowSpell, не забыв исправить константы skillMax и MaxSkills:
Здесь видно, что мы запретили работать с заклинаниями воину, просто обнулив константы уровня навыка. То есть воин может пытаться "метнуть" заклинание, но у него просто никогда ничего не получится. Такой подход более гуманен, нежели прямой запрет на доступ к магии - непонятно, почему человек-стрелок может хотя бы пытаться работать волшебником, а человек-воин - нет.
Вызов процесса заклинания будем осуществлять по нажатию на клавишу 't':
case k of
...
' t ' : ThrowSpell;
Для магов подготовим отдельный модуль, назовем его Magik:
unit Magik;
interface
implementation
end.
В нем и разместим заголовок и реализацию процедуры ThrowSpell. Она должна прежде всего проверить соответствующий навык:
interface
procedure ThrowSpell;
implementation uses Hero, LowLevel, Texts;
{ ----------------- } procedure ThrowSpell; begin if not SkillTest(Heroes[CurHero], skillThrowSpell) then begin
ShowInfo(STR_MAGIK_BADTEST);
Exit end;
Тут мы использовали текстовую константу (размещаем ее, как обычно, в Texts):
STR_MAGIK_BADTEST = ' Попытка броска заклинания неудачна. ' ;
Расширим процедуру SkillTest новым навыком:
... case skl of
skillHandWeapon,
skillRangedWeapon,
skillThrowSpell,
skillDefence: begin
SuccessSkillTest(H, skl); end;
...
Добавим код увеличения навыка в процедуру SuccessSkillTest:
skillThrowSpell: begin if random(50) = 0 then begin
ShowInfo(STR_THROWSPELLSKILL_OK);
inc(H.Skills[skillThrowSpell]); end; end;
Теперь вернемся к основной процедуре и продолжим реализацию магии. Нам надо предложить играющему на выбор список доступных заклинаний и зафиксировать его решение. Для этого, очевидно, необходимо какое-то формальное описание структуры заклинания. Введем его в качестве нового типа. Что понадобится в программе для магии в первую очередь? Это наверняка количество маны, необходимое для производства заклинания, его текстовое название, а также, видимо, какие-то дополнительные числовые характеристики, специфические
для конкретного заклинания. Последние характеристики мы представим в виде универсального массива (по аналогии с Ints в структуре героя), и будем с ним работать схожим методом.
const MaxSpellInt = 10;
type TSpell = record
Name: String[30];
Mana: Integer;
Ints: array[1..MaxSpellInt] of Integer; end;
Далее сразу подготовим массив-константу описаний типов заклинаний (по аналогии с массивом описаний типов предметов):
Следующий шаг - создание интерфейсной функции, которая будет запрашивать выполняемое заклинание. Пусть она вернет номер заклинания в массиве Spells или 0, если ничего не выбрано. Назовем эту функцию GetSpellNo, а разместим ее в модуле LowLevel.
{ ----------------- } function GetSpellNo: Integer; var i: Integer; begin
ClrScr; GoToXY(1,2);
for i := 1 to MaxSpells do
WriteLn(i, ' ) ' ,Spells[i].Name, ' : ' ,Spells[i].Mana);
WriteLn(STR_SPL_GETNUM);
ReadLn(i);
GetSpellNo:=0; if not (i in [1..MaxSpells]) then
Exit;
GetSpellNo:=i;
end;
Текстовая константа:
STR_SPL_GETNUM = ' Введите номер функции или 0: ' ;
Затем необходимо проверить, достаточно ли у героя маны:
{ ----------------- } procedure ThrowSpell; var n: Integer; begin
n := GetSpellNo; if n=0 then Exit;
if Heroes[CurHero].Mana < Spells[n].Mana then begin
ShowInfo(STR_MAGIK_BADMANA);
Exit end;
dec(Heroes[CurHero].Mana, Spells[n].Mana);
Текстовая константа:
STR_MAGIK_BADMANA= ' Недостаточно маны! ' ;
Обратите внимание, что действия по выбору заклинания и проверке уровня маны выполняются до процедуры проверки успешности навыка, который может привести к изменению внутренних параметров героя, когда само заклинание может оказаться недоступным.
Наконец, наступает процесс реализации самих заклинаний. Подготовим для этого оператор выбора (в нем используются две новые константы-индексы заклинаний в массиве Spells):
Как будет работать огненный шторм? Надо перебрать все окружающие героя клетки, проверить, есть ли на них монстры, и нанести каждому некоторое поражение. Напомним, что у нас есть удобная процедура HeroAttackFin, которая учитывает воздействие заданной величины поражения на монстра. Только ее заголовок надо записать в интерфейсном разделе модуля Combat, и подключить сам модуль в раздел реализации Magik. Кроме того, после атаки монстров надо передать им ход - вызвать процедуру MonstersStep. Только делать это надо
не каждый раз после удара по конкретному монстру, а после обработки всех попавших под атаку монстров. Кроме того, если ни один монстр под влияние огненного шторма не попал, ответный ход противника не нужен.
Для реализации такого алгоритма добавим локальную переменную-флажок f, которая будет принимать true, когда какой-нибудь монстр попадет под удар:
splFireStorm: begin
f := false; for x := Heroes[CurHero].x-1 to Heroes[CurHero].x+1 do for y := Heroes[CurHero].y-1 to Heroes[CurHero].y+1 do begin
n := IsMonsterOnTile(x,y); if n > 0 then begin
dam := random(4)+1;
HeroAttackFin(Heroes[CurHero],n,dam);
f := true; end; end;
if f then
MonstersStep;
end;
Механизм исцеления пусть работает так. Герой теряет одну единицу маны, и восстанавливает одну единицу здоровья. При этом данное заклинание злости у монстров не вызовет, то есть его можно будет выполнять сколько угодно раз подряд (сколько позволит мана), не опасаясь атаки стоящих рядом врагов.
splSelfHealing: begin
dec(Heroes[CurHero].Mana);
IncHP(Heroes[CurHero], +1);
ShowInfo(STR_SELFHEALING); end;
Текстовая константа:
STR_SELFHEALING = ' Самоисцеление успешно! ' ;
Попрактиковавшись с доступными на данный момент классами, можно заметить один недостаток, который стал особо явным после создания персонажа-мага: ману постоянно надо восстанавливать, да и здоровье тоже неплохо было бы увеличивать также каким-то боле естественным путем, а не только через источники. Кстати, в источниках нам надо реализовать также восстановление маны, а сделать это совсем просто:
... if GameMap[CurMap].Cells[ Heroes[CurHero].x,Heroes[CurHero].y].Tile in
LiveTileSet then begin
ShowInfo(STR_LIVE);
IncHP( Heroes[CurHero], Heroes[CurHero].MaxHP );
IncMana( Heroes[CurHero], Heroes[CurHero].MaxMana ); end;
(код, расширенный вызовом IncMana в процедуре MoveHero модуля Game).
А новая процедура IncMana (увеличение уровня маны героя) запишется в модуле Hero:
{ ----------------- } procedure IncMana( var H: THero; mad: Integer ); begin
H.Mana := H.Mana + mad; if H.Mana > H.MaxMana then H.Mana := H.MaxMana; end;
Теперь при вступлении в источник у персонажа восстановится как уровень здоровья, так и уровень маны - до максимума.
Далее - обучаемся работе с Магическими предметами.
Исходный код текущей версии для Turbo Pascal (всегда проверен и работоспособен, главный файл - main.pas):