Урок 23. Готовимся к стрельбе из лука
Перед выводом
статистики немного улучшим существующий код. Пока у нас не реализован момент
роста уровня героя при увеличении опыта (процедура IncXP в модуле Него). Вот
как мы его запрограммируем:
procedure IncXP(
var H: THero;
axp: Integer );
begin
inc ( H.Exp,
axp );
ShowInfo(STR_ADD_EXP +
IntToStr(axp));
if H.Exp > H.MaxExp then
begin
H.Exp := 0;
if H.Level < MaxPlayerLevel then
begin
Showlnfo(STR_NEXTLEVEL);
inc(H.Level);
end;
H.MaxExp := ExpLevel_Table[H.Level];
H.MaxHP := HPLevel_Table[H.Level];
H.HP := H.MaxHP;
end;
end;
Если превышен
максимальный уровень экспы»(опыта), увеличиваем уровень (если он уже достигнут
- максимально возможный), а на его основе пересчитываем следующий порог опыта,
а также проверяем повышенный порог здоровья.
Информацию о
герое мы будем выводить с помощью процедуры ShowHeroFullInfo, которую расположим
конечно в модуле LowLevel, так какзто задача, непосредственно связанная с пользовательским
интерфейсом:
procedure
ShowHeroFullInfo(HeroNum: Integer);
begin
end;
Действовать
она будет так - сперва мы очистим экран покажем все нужные и важные сведения нашему
герою, а затем восстановим полную карту с помощью известной функции:
Procedure ShowHeroFullInfo;
Begin
…
…(1,1);
…
…
End;
При нажатии
клавиши будем вызывать ее? Допустим, по нажатию на символ 'с', для чего дополним
цикл обработки событий в модуле Main еще одним пунктом:
While true do
Begin
…me
I:=readkey;
Case I of
‘ c ’: ShowHeroFullInfo;
‘ g ’: GetItemFromMap;
End;
Мы желаем
знать, прежде всего, значения всех его базовых параметров и навыков, по
здоровью и опыту, уровень и другие дополнительные сведения. Сначала достанем
параметры из массива Chars и навыки из Skills (для отображения слотов и предметов
у нас уже есть процедуры ShowHеroSlots и ShowHeroItems). Для этого, прийдется
подготовить в модуле Texts массив текстовых констант.
Получим
название каждого параметра и каждого навыка (в соответствии с порядоком, елементами
chr.... skill... в
модуле Неrо). Здесь мы
вновь столкнемся с проблемой по подготовке массива SlotName с названиями слотов
- из-за того, мы будем ссылаться с интерфейсной части одного модуля на
интерфейсную часть конструкции подключаемых модулей модуля Неrо уже указан модуль Texts. Перменные MaxChars и MaxSkills надо
будет перенести в модуль Texts.
Обратимся к массивам с
названиями соответствующих особенностей героя:
…
… :array[1..MaxChars] of string[20] =
{
‘Ловкость' ,
' Тело '
, ' Ум
' , '
Мудрость ' ,
'
}
…
…: array[1..MaxSkills] of string[20]
=
{
…, ‘Поиск оружия’, ‘Защита’
}
В.модуле Hero раздел
констант немного сократится:
const chrSTR
= 1;
chrDEX = 2;
chrCON - 3;
chrlQ = 4;
chrWIS = 5;
chrCHA = 6;
skillHandWeapon = 1;
skillTrapSearch = 2;
skillDefence = 3;
При этом компилятор
нам дополнительно сообщит, что в модуле Tables теперь не определена константа MaxSkills, и поэтому в заголовок
надо добавить ссылку и на Texts
-новое место этой константы:
unit Tables;
interface uses Hero,
Texts;
Теперь нам
осталось удостовериться в правильности этих действий, для чего продолжим
процедуру отображения статистик выводом значения навыков:
procedure ShowHeroFullInfo;
var
i: Integer;
begin
ClrScr;
GoToXY(l,1);
WriteLn(STR_HEROFI) ;
for
i := 1 to
MaxChars do
WriteLn ( CharsName[i], '
: ' , Heroes[CurHero]
.Chars[i] );
WriteLn; WriteLn(STR_HER0FI2);
for
i := 1 to
MaxSkills do
WriteLn ( SkillsName[i], '
: ' ,
Heroes[CurHero] .Skills[i] );
ReadLn;
ShowGame;
end; Здесь
использована две константы (модуль Texts):
STR_HEROFI =
' Параметры героя: '
; STR_HEROFI2 = '
Навыки героя: ' ;
Что еще будет
нас интересовать? Это, без сомнения, уровень героя, текущий набранный опыт и
остаток до следующего уровня. Эти данные уже хранятся в структуре ТНего,
поэтому итоговый вариант процедуры, не нуждающийся в комментариях, запишется
так:
procedure ShowHeroFullInfo;
var
i: Integer;
begin
ClrScr;
GoToXY(l,1);
WriteLn(Heroes[CurHero].Name,',',RaceName[Heroes[CurHero].Race], ' '
,ClassName[Heroes[CurHero].Class]);
WriteLn(STR_HEROFI_LEVEL, Heroes[CurHero].Level);
WriteLn(STR_HEROFI_EXP, Heroes[CurHero].Exp, ' /
' ,Heroes[CurHero].MaxExp);
WriteLn;
WriteLn(STR_HEROFI);
for
i := 1 to
MaxChars do
WriteLn( CharsName[i], '
: ' ,
Heroes[CurHero].Chars[i] );
Writeln(STR_HER0Fl2);
Fo I:=1 to MaxSkills do
Writeln( SkillsName[i], ' : ' ,
Heroes[CurHero].Skills[i] );
…
End;
Стрельба из
лука
Следующим
шагом по улучшению нашей игры станет реализация дальнобойного оружия. Что позволит
герою вести огонь по противнику с расстояния, однако такая возможность давала
бы персонажу слишком большие преимущества перед монстрами, которые сражаются в
рукопашную, поэтому на стрельбу нам придется наложить ограничения. Какими они
могут быть?
Нам нужны патроны,
стрелы, камни, - другими словами, боеприпасы. Для этого создаются два новых
типа предметов, назовем их itemAmmo (боеприпасы) и IWeapon (дальнобойное оружие):
WeaponItemType =
(itemHandWeapon, itemArmor,
itemAmmo, itemRangedWeapon, itemNone);
…item)
Сделаем код
таким, чтобы дополнение тех или иных понятий новыми сущностями имело минимальные
сообщения об отсутствии их реализации автоматически. Теперь добавим две новые
константы в описание типа TGameltemTypc, мы если будем компелировать программу,
то сразу получим сообщение об ошибке. В …Level имеется массив констант ItemRecords, содержащих символы-
соотеветвующих предметов. Расширим его двумя новыми позициями. Пусть они
отделяются символом '|', а дальнобойное оружие (лук) - символом '}'.
ItemRacords: array[TGameltemType] of TTileRecord =
{
‘ | ’ , Clr:laghtCyan),
‘ | ’ , Clr:LightGreen),
‘ | ’ , Clr: lightCyan) ,
‘ | ’ , Clr:LihtGreen) ,
‘ | ’ , Clr:Black)
}
Пускай они
разбрасываются по карте равномерно и автоматически, так что в нашем мире можно
будет найти и стрелы, и луки.
Данные предметов добавляются в
массив Item Types. Прежде всего увеличим на два MaxItmeTypes – до
6.
MaxItemTypes:=6;
…
…Types:array[1..MaxItemTypes] of TGameltem =
{
…
…ItemAmmo;
…ItemAmmo;
…(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
…(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
…
…
…RangeWeapon;
…
…(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
Reals: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0))
}
Константы в
модуле Texts описывают название новых типов предметов:
STR_AMMO
= '
Стрелы ' ;
STR_CROSS =
' Лук ' ;
Условимся,
что для боеприпасов первый элемент вложенного массива Ints будет означать число
стрел в пакете, а для луков также первый элемент покажет число накопленных для
него стрел. Для этого, как мы раньше делали, опишем две константы-индекса
соответствующих позиций в этих массивах:
const
…
intAmmo
= 1; intRangedAmmo - 1;
Взятие лука с
земли и перемещение его из инвентаря в руки будет происходить так же, как и с
другими вещами. Возможность перемещения в руки у нас задается специально для
этого предназначенной функцией GoodSlot (модуль Него). Нам только надо
дополнить список типов предметов, которые можно держать в руках, новым типом
дальнобойного оружия:
function GoodSlot(Slot: Integer;
Itm: TGameltem): Boolean;
begin
GoodSlot := false;
case Slot of slotBody : GoodSlot :=
Itm.TType in [itemArmor];
slotHands: GoodSlot := Itm.IType in
[itemHandWeapon,
itemRangedWeapon];
end;
end;
Кроме того,
если мы выбрали в качестве класса героя стрелка, желательно, чтобы он исходно
уже был экипирован не топором, а луком. Пока единый способ размещения топора и
бронежилета у нас происходит в процедуре InitHero, но трогать ее мы не будем, а
сразу перейдем к новой процедуре генерации героя, где после выбора класса
уточним предметы, выдаваемые лучнику. Позже аналогичные манипуляции мы
произведем и для мага. Пока же добавим в конец процедуры GenerateHero следующий
код:
…
if Heroes[CurHero].Class = classRanger
then
begin
Heroes[CurHero].Items[1] :=
ItemTypes[6];
end;
end;
Здесь мы
перезаписываем уже размещенный первым топор на лук (6-й в массиве типов
предметов). Кроме того, этому луку мы придаем 10 стрел, используя константу
intRangedAmmo.
С
перемещением из инвентаря "в руки" боеприпасов придется поступить по
другому. Нам потребуется дополнительно проверить, находится ли в руках
дальнобойное оружие, после чего загрузить в него нужное количество стрел, а сам
предмет удалить. В перспективе вероятна необходимость дополнительного анализа
соответствия типа боеприпасов типу оружия - ведь из пращи нельзя стрелять
стрелами, а стрелы для легкого лука, возможно, будут отличаться отстрел к
тяжелому арбалету.
Вводя данную дополнительную
возможность в игру, мы рано или поздно столкнемся с необходимостью отображения
более подробной информации о предметах, хранимых в инвентаре или весомых на
теле. Пока для этого мы просто выводим название предмета, однако уже сейчас
есть необходимость как минимум отображения числа стрел для двух новых типов
предметов.
В модуле GameItem добавим
функцию, которая будет выдавать значение предмета. В ней будут собраны все
сведения о всех предметах, и в процедурах, наподобие ShowHeroitems достаточно будет внести
обращения к программной функции генерации описания.
Procedure
GetItemName(itm:TGameItem): String;
Begin
Case Item.Type of
Writeln( GetItemName: itm.Name+
‘ | ’ + inttostr(Itm.Ints[intAmmo])+
‘)’;
GetItemName:=itm.Name+’|’+inttostr(Itm.Ints[intRangerAmmo])+’)’;
… GetItemName:=Itm.Name
End;
End;
Добавим заголовок
функции в раздел интерфейса, а в раздел реализации подлючим модуль Iowlумуд для
доступа к функции IntToStr. Теперь следом за названим программа будет сообщать число стрел, в круглых скобочках.
… данной
функции разместим в процедурах работы с инвентарем и экипировкой, … старых выводов
названий предметов напрямую.
Procedure ShowHeroSlots:
If Heroes[CurHero].Slots [i] .IType = itemNone
Then Write(STR_EMPTY_ITEM)
Else Write(GetItemName (Heroes [CurHero]
.Slots [i] ) )
Procedure ShowHeroItems:
If Heroes[CurHero].Slots [i] .IType = itemNone
Then Write(i, ‘)’, STR_EMPTY_ITEM)
Else Write(i, ‘)’, GetItemName (Heroes
[CurHero] .Slots [i] ) )
Теперь лук
находится в руках, и для него приготовлены стрелы, герой должен стрелять из
него и поражать противника на расстоянии.
В зависимости
от меткости и силы стрельбы.
Источник: delgame.at.ua
|