Давайте сделаем рогалик (Книги заклинаний)

spellbook.png

Мы подошли к тому моменту, когда можем реализовать в нашей игре заклинания. Реализация заклинаний будет заключать в себе две части, это обучение заклинанию, и, непосредственно, использование заклинания. В данной главе мы реализуем обучение персонажа новым заклинаниям, а использование оставим для следующей главы. Прежде чем мы приступим к написанию кода, позвольте мне дать вам некоторое представление о процессе обучения новым заклинаниям.

Персонаж начинает игру не зная никаких заклинаний, и, очевидно, не может пользоваться заклинаниями, пока не изучит их. Чтобы выучить новое заклинание, персонаж должен найти книгу с заклинанием и прочитать ее. Когда персонаж находит книгу заклинаний, она не опознана и не может быть прочитана. Только опознанные книги можно читать. Прочитав опознанную книгу, персонаж выучивает заклинание, которое в ней содержится, которое добавляется в список известных заклинаний.

Если книга содержит заклинание которое персонаж уже знает, то уровень этого заклинания увеличивается на 1. Уровень заклинания влияет на силу эффекта этого заклинания, поэтому, для получения более мощных заклинаний персонаж будет вынужден читать книги с уже изученными заклинаниями. Однако, что бы добавить немного более интереса игре, некоторые книги будут пустыми, но персонаж не будет этого знать пока книга не будет опознана. Это сделает процесс изучения заклинаний немого более интересным. Существует 50% шанс, что книга окажется пустой, поэтому игрок никогда не будет знать, содержит ли только что подобранная книга необходимые ему заклинания, или, всего лишь, пустые страницы.

Когда мы перейдем к коду, вы увидите, что книга заклинаний, это всего лишь контейнер, который может содержать какое либо заклинание. Сама книга ничего не делает, она просто содержит в себе заклинание, которое, после прочтения, попадает в список известных заклинаний персонажа. Для того, чтобы облегчить нам работу, заклинания будут выглядеть как предметы инвентаря, содержащиеся в отдельном массиве, с назначенными для них буквами верхнего регистра, от 'A' до 'Z'. Создав заклинания как объекты инвентаря, мы можем использовать наш существующий код работы с инвентарям, а не придумывать новый велосипед.

Поскольку книга заклинаний является контейнером для заклинания, то давайте, прежде чем перейдем к реализации книг, посмотрим на новый список заклинаний.

inv.bi

Enum spellid
    ...
    splAcidFog     'КнигаЗакл.: 5 повреждений каждые lvl ходов
    splFireCloak   'КнигаЗакл.: Цель получает 10 повреждений каждые lvl ходов
    splHeal        'КнигаЗакл.: Восстановление здоровья на 1% * lvl
    splMana        'КнигаЗакл.: Восстановление маны 1% * lvl (не потребляет ману)
    splRecharge    'КнигаЗакл.: перезарядить жезл на 1 * lvl зарядов
    splFocus       'КнигаЗакл.: +lvl ко всем боевым факторам на 1 ход
    splLightning   'КнигаЗакл.: 2 * lvl повреждений (игнорируя броню)
    splBlind       'КнигаЗакл.: Ослепляет цель на lvl ходов
    splTeleport    'КнигаЗакл.: Телепорт в выбранную точку lvl - дистанция (должна быть видимой).
    splOpen        'КнигаЗакл.: Отпирает запертую дверь (lvl против Двери).
    splFear        'КнигаЗакл.: Обращает монстра в бегство на lvl ходов.
    splConfuse     'КнигаЗакл.: Дезориентирует монстра на lvl ходов.
    splFireBomb    'КнигаЗакл.: Повреждение по площади 20 * lvl. Поджигает монстров на  lvl ходов.
    splEntangle    'КнигаЗакл.: Сковывает цель на lvl ходов, нанося lvl повреждений каждый ход.
    splCloudMind   'КнигаЗакл.: Цель не может колдовать lvl ходов.
    splFireball    'КнигаЗакл.: Повреждение по площади 10 * lvl. Поджигает монстров на lvl ходов.
    splIceStatue   'КнигаЗакл.: Заморозить цель на lvl ходов. Замороженные могут быть убиты с одного удара.
    splRust        'КнигаЗакл.: Уменьшить броню цели на lvl * 10%.
    splShatter     'КнигаЗакл.: Уничтожить оружие цели, если возможно.
    splMagicDrain  'КнигаЗакл.: Уменьшает MDF цели на lvl% и добавляет заклинателю на 1 ход.
    splPoison      'КнигаЗакл.: Отравляет цель по 1 HP lvl ходов.
    splEnfeeble    'КнигаЗакл.: Уменьшает боевой фактор цели на lvl * 10%.
    splShout       'КнигаЗакл.: Оглушить всех видимых монстров на lvl ходов.
    splStealHealth 'КнигаЗакл.: Уменьшает HP lvl% цели и добавляет заклинателю.
    splMindBlast   'КнигаЗакл.: Уменьшает MCF и MDF цели на lvl% на lvl ходов.
    splBlink       'КнигаЗакл.: Телепорт в случайное место. Lvl - дистанция.
  End Enum

Это список заклинаний, которые персонаж может выучить. 26 заклинаний займут 26 мест на экране инвентаря. Как только персонаж изучит заклинание, определенная позиция в списке заполниться. Так как нам понадобятся названия заклинаний и их описания, то нам необходимо обновить функции для их получения.

inv.bi
'Возвращает название заклинания.
  Function GetSpellName(spl As spellid) As String
    Dim As String ret = ""
     
    Select Case spl
      ...
      Case splAcidFog     'КнигаЗакл.: 5 повреждений каждые lvl ходов
         ret = "Spell of Acid Fog"
      Case splFireCloak   'КнигаЗакл.: Цель получает 10 повреждений каждые lvl ходов
         ret = "Spell of Fire Cloak"
      ...
      Case Else
         ret = "No spell."
    End Select
 
    Return ret
  End Function
 
  'Возвращает описание действия заклинания.
  Function GetSpellEffect(spl As spellid) As String
    Dim As String ret = ""
     
    Select Case spl
      ...
      Case splAcidFog     'КнигаЗакл.: 5 повреждений каждые lvl ходов
         ret = "Target receives 5 damage for level turns."
      Case splFireCloak   'КнигаЗакл.: Цель получает 10 повреждений каждые lvl ходов
         ret = "Target receives 10 damage over level turns."
      ...
      Case Else
         ret = "No effect."
    End Select
     
    Return ret
  End Function

Здесь показаны вносимые изменения только для пары заклинаний, для того, чтобы дать вам представление о необходимых изменениях. В дополнении к этим функциям, нам понадобиться еще одна - GetSpellShortName, которая будет возвращать короткое название заклинания.

inv.bi
'Возвращает короткое название заклинания.
  Function GetSpellShortName(spl As spellid) As String
    Dim As String ret = ""
     
    Select Case spl
      Case splMaxHealing  'Восстановить HP до максимума.
         ret = "Healing"
      Case splStrongMeat 'Добавить бонус силы на время.
         ret = "Strength"
      Case splBreadLife   'Вылечить отравление.
         ret = "C Poison"
      Case splMaxMana     'Полностью восстановить ману.
         ret = "R Mana"
      Case splSerpentBite 'Оружие: наносит повреждение ядом.
         ret = "S Bite"
      Case splRend        'Оружие: Уменьшает броню цели.
         ret = "R Armor"
      Case splSunder      'Оружие: Уменьшает наносимые целью повреждения орудием.
         ret = "Sunder"
      Case splReaper      'Оружие: Обращает в бегство.
         ret = "Reaper"
      Case splFire        'Оружие: Поджигает цель на lvl ходов.
         ret = "Fire"
      Case splGoliath     'Оружие: Добавляет силу при атаке.
         ret = "Goliath"
      Case splStun        'Оружие: Оглушает цель на lvl ходов.
         ret = "Stun"
      Case splChaos       'Оружие: Дополнительные повреждения на случайную величину.
         ret = "Chaos"
      Case splWraith      'Оружие: Уменьшает случайную характеристику цели.
         ret = "Wraith"
      Case splThief       'Оружие: Крадет случайную характеристику у цели и добавляет персонажу.
         ret = "Phantom"
      Case splNoSlash     'Броня: +% от рубящих атак.
         ret = "Slash Pr"             
      Case splNoCrush     'Броня: +% от дробящих атак.
         ret = "Crush Pr"
      Case splNoPierce    'Броня: +% от колотых атак.
         ret = "Pierce P"
      Case splNoEnergy    'Броня: +% от энергетически атак.
         ret = "Energy P"
      Case splNoMagic     'Броня: +% от магических атак.
         ret = "Magic Pr"
      Case splNoFire      'Броня: +% от огненных атак.
         ret = "Fire Res"
      Case wdAcid         'Броня: +% от кислотных атак.
         ret = "Res Acid"
      Case splUCF         'Драгоценности: +% к UCF.
         ret = "UCF"
      Case splACF         'Драгоценности: +% к ACF.
         ret = "ACF"
      Case splPCF         'Драгоценности: +% к PCF.
         ret = "PCF"
      Case splMCF         'Драгоценности: +% к MCF.
         ret = "MCF"
      Case splCDF         'Драгоценности: +% к CDF.
         ret = "CDF"
      Case splMDF         'Драгоценности: +% к MDF.
         ret = "MDF"
      Case splRegenHP     'Драгоценности: +% к лечению в ход.
         ret = "Rgn Hlth"
      Case splRegenMana   'Драгоценности: +% к регенерации маны в ход.
         ret = "Rgn Mana"
      Case splAcidFog     'КнигаЗакл.: 5 повреждений каждые lvl ходов
         ret = "Acid Fog"
      Case splFireCloak   'КнигаЗакл.: Цель получает 10 повреждений каждые lvl ходов
         ret = "F Cloak"
      Case splHeal        'КнигаЗакл.: Восстановление здоровья на 1% * lvl
         ret = "Healing"
      Case splMana        'КнигаЗакл.: Восстановление маны 1% * lvl (не потребляет ману)
         ret = "R Mana"
      Case splRecharge    'КнигаЗакл.: Перезарядить жезл на 1 * lvl зарядов
         ret = "Rec Wand"
      Case splFocus       'КнигаЗакл.: +lvl ко всем боевым факторам на 1 ход
         ret = "Focus"
      Case splLightning   'КнигаЗакл.: 2 * lvl повреждений (игнорируя броню)
         ret = "Lightng"
      Case splBlind       'КнигаЗакл.: Ослепляет цель на lvl ходов
         ret = "Blind"
      Case splTeleport    'КнигаЗакл.: Телепорт в выбранную точку lvl - дистанция (должна быть видимой).
         ret = "Teleport"
      Case splOpen        'КнигаЗакл.: Отпирает запертую дверь (lvl против Двери).
         ret = "Op Door"
      Case splFear        'КнигаЗакл.: Обращает монстра в бегство на lvl ходов.
         ret = "Fear"
      Case splConfuse     'КнигаЗакл.: Дезориентирует монстра на lvl ходов.
         ret = "Confuse"
      Case splFireBomb    'КнигаЗакл.: Повреждение по площади 20 * lvl. Поджигает монстров на  lvl ходов.
         ret = "F Bomb"
      Case splEntangle    'КнигаЗакл.: Сковывает цель на lvl ходов, нанося lvl повреждений каждый ход.
         ret = "Entangle"
      Case splCloudMind   'КнигаЗакл.: Цель не может колдовать lvl ходов.
         ret = "Cld Mind"
      Case splFireball    'КнигаЗакл.: Повреждение по площади 10 * lvl. Поджигает монстров на lvl ходов.
         ret = "F Ball"
      Case splIceStatue   'КнигаЗакл.: Заморозить цель на lvl ходов. Замороженные могут быть убиты с одного удара.
         ret = "I Statue"
      Case splRust        'КнигаЗакл.: Уменьшить броню цели на lvl * 10%.
         ret = "Rust"
      Case splShatter     'КнигаЗакл.: Уничтожить оружие цели, если возможно.
         ret = "Shatter"
      Case splMagicDrain  'КнигаЗакл.: Уменьшает MDF цели на lvl% и добавляет заклинателю на 1 ход.
         ret = "Dr Magic"
      Case splPoison      'КнигаЗакл.: Отравляет цель по 1 HP lvl ходов.
         ret = "Poison"
      Case splEnfeeble    'КнигаЗакл.: Уменьшает боевой фактор цели на lvl * 10%.
         ret = "Enfeeble"
      Case splShout       'КнигаЗакл.: Оглушить всех видимых монстров на lvl ходов.
         ret = "Shout"
      Case splStealHealth 'КнигаЗакл.: Уменьшает HP lvl% цели и добавляет заклинателю.
         ret = "Vampire"
      Case splMindBlast   'КнигаЗакл.: Уменьшает MCF и MDF цели на lvl% на lvl ходов.
         ret = "M Blast"
      Case splBlink       'КнигаЗакл.: Телепорт в случайное место. Lvl - дистанция.
         ret = "Blink"    
      Case Else
         ret = "No spell."
    End Select
 
    Return ret
  End Function

Поскольку в экране инвентаря недостаточно места для отображения полных названий заклинаний, мы будем использовать сокращенные названия в качестве мнемоник. Игрок всегда может осмотреть заклинание для того, чтобы узнать его полное название и описание. Обратите внимание, что я добавил короткие имена для всех заклинаний, а не только для тех, которые заучиваются через книги. На данном этапе, нам они не понадобятся, но, если возникнет необходимость в коротких именах для, например, заклинаний ювелирных изделий, то мы будем к этому готовы.

Вооружившись списком заклинаний, мы можем создать несколько магических книг.

inv.bi
'Создает новый предмет — магическую книгу.
  Sub GenerateSpellBook(inv As invtype, currlevel As Integer)
    Dim item As spellbkids
     
    'Назначим тип предмета.
    inv.classid = clSpellBook
    'Два типа книг, пустая и содержащая заклинание
    item = RandomRange(bkSpellBlank, bkSpellBook)
    'Общие поля.
    inv.spellbook.evaldr = GetScaledFactor(charint, currlevel)
    inv.spellbook.eval = FALSE
    inv.spellbook.id = item
    inv.iconclr = fbOrange
    inv.icon = Chr(254)
    inv.spellbook.use = useRead
    inv.spellbook.noise = 1
    inv.desc = "Spell Book"
    'Если книга не пустая, то сгенерируем для нее заклинание.
    If item = bkSpellBook Then
      'Получим заклинание.
      inv.spellbook.spell.id = RandomRange(splAcidFog, splBlink) 'Spell id.
      GenerateSpell inv.spellbook.spell, currlevel
      inv.spellbook.spell.lvl = 1
      inv.spellbook.spell.dam = 0
      'Назначим наносимые повреждения/кол-во маны для заклинаний.
      Select Case inv.spellbook.spell.id
         Case splAcidFog     'КнигаЗакл.: 5 повреждений каждые lvl ходов
            inv.spellbook.spell.dam = 5
            inv.spellbook.spell.manacost = 5 
         Case splFireCloak   'КнигаЗакл.: Цель получает 10 повреждений каждые lvl ходов
            inv.spellbook.spell.dam = 10
            inv.spellbook.spell.manacost = 10 
         Case splLightning   'КнигаЗакл.: 2 * lvl повреждений (игнорируя броню)
            inv.spellbook.spell.dam = 2
            inv.spellbook.spell.manacost = 6 
         Case splFireBomb    'КнигаЗакл.: Повреждение по площади 20 * lvl. Поджигает монстров на lvl ходов.
            inv.spellbook.spell.dam = 20
            inv.spellbook.spell.manacost = 20
         Case splEntangle    'КнигаЗакл.: Сковывает цель на lvl ходов, нанося lvl повреждений каждый ход.
            inv.spellbook.spell.dam = 4
            inv.spellbook.spell.manacost = 4
         Case splFireball    'КнигаЗакл.: Повреждение по площади 10 * lvl. Поджигает монстров на lvl ходов.
            inv.spellbook.spell.dam = 10
            inv.spellbook.spell.manacost = 15
         Case splRust        'КнигаЗакл.: Уменьшить броню цели на lvl * 10%.
            inv.spellbook.spell.manacost = 8
         Case splMagicDrain  'КнигаЗакл.: Уменьшает MDF цели на lvl% и добавляет заклинателю на 1 ход.
            inv.spellbook.spell.manacost = 3
         Case splPoison      'КнигаЗакл.: Отравляет цель по 1 HP lvl ходов.
            inv.spellbook.spell.dam = 1
            inv.spellbook.spell.manacost = 3
         Case splEnfeeble    'КнигаЗакл.: Уменьшает боевой фактор цели на lvl * 10%.
            inv.spellbook.spell.manacost = 9
         Case splHeal        'КнигаЗакл.: Восстановление здоровья на 1% * lvl
            inv.spellbook.spell.manacost = 4
         Case splMana        'КнигаЗакл.: Восстановление маны 1% * lvl (не потребляет ману)
            inv.spellbook.spell.manacost = 0
         Case splRecharge    'КнигаЗакл.: Перезарядить жезл на 1 * lvl зарядов
            inv.spellbook.spell.manacost = 2 
         Case splFocus       'КнигаЗакл.: +lvl ко всем боевым факторам на 1 ход
            inv.spellbook.spell.manacost = 12
         Case splBlind       'КнигаЗакл.: Ослепляет цель на lvl ходов
            inv.spellbook.spell.manacost = 6
         Case splTeleport    КнигаЗакл.: Телепорт в выбранную точку lvl - дистанция (должна быть видимой).
            inv.spellbook.spell.manacost = 4
         Case splOpen        'КнигаЗакл.: Отпирает запертую дверь (lvl против Двери).
            inv.spellbook.spell.manacost = 2 
         Case splFear        'КнигаЗакл.: Обращает монстра в бегство на lvl ходов.
            inv.spellbook.spell.manacost = 4
         Case splConfuse     'КнигаЗакл.: Дезориентирует монстра на lvl ходов.
            inv.spellbook.spell.manacost = 9 
         Case splCloudMind   'КнигаЗакл.: Цель не может колдовать lvl ходов.
            inv.spellbook.spell.manacost = 12 
         Case splIceStatue   'КнигаЗакл.: Заморозить цель на lvl ходов. Замороженные могут быть убиты с одного удара.
            inv.spellbook.spell.manacost = 14 
         Case splShatter     'КнигаЗакл.: Уничтожить оружие цели, если возможно.
            inv.spellbook.spell.manacost = 5 
         Case splShout       'КнигаЗакл.: Оглушить всех видимых монстров на lvl ходов.
            inv.spellbook.spell.manacost = 12 
         Case splStealHealth 'КнигаЗакл.: Уменьшает HP lvl% цели и добавляет заклинателю.
            inv.spellbook.spell.manacost = 8 
         Case splMindBlast   'КнигаЗакл.: Уменьшает MCF и MDF цели на lvl% на lvl ходов.
            inv.spellbook.spell.manacost = 12 
         Case splBlink       'КнигаЗакл.: Телепорт в случайное место. Lvl - дистанция.
            inv.spellbook.spell.manacost = 4
         Case Else
            inv.spellbook.spell.splname = "Unknown Spell"
            inv.spellbook.spell.splsname = "Unknown"
      End Select
    Else
      ClearSpell inv.spellbook.spell
    End If
  End Sub

Как вы можете видеть, мы следуем той же схеме, как и в остальных подпрограммах генерации. Мы задаем новый флаг useRead, который служит для проверки заклинаний при использовании игроком команды инвентаря «Прочитать». На всякий случай, если все будет работать не совсем так, как мы рассчитывали, у нас есть ветка Else в секции Select Case, необходимая для того, чтобы мы легче выявили возможную причину ошибок. Мы добавили такую же ветвь во все остальные подпрограммы генерации, что является хорошей практикой. Это нисколько не скажется на производительности и не будет занимать какое либо ощутимое место в оперативной памяти, но значительно упростит поиск ошибок. Такие проверки должны стать стандартом в ваших программах. Когда мы вернемся к коду, через какое то время, чтобы что либо изменить, мы всегда будем уверены, что если что то сломается, то у нас сработает защитный механизм.

Конечно, нам необходимо обновить все подпрограммы и функции работы с инвентарем, чтобы добавить новый тип предмета — магические книги, но мы делали это уже столько много раз, что я не буду здесь это показывать. Настоящие действия происходят на экране работы с инвентарем.

dod.bas
'Управление инвентарем персонажа.
  Sub ManageInventory()
    Dim As String kch, ich
    Dim As Integer ret
     
    DrawInventoryScreen
    Do
      kch = Inkey
      kch = Ucase(kch)
      'Если символ введен.
      If kch <> "" Then
         'Команда распознания.
         If kch = "V" Then
            ret = ProcessEval()
            'Экран изменился, перерисуем.
            If ret = TRUE Then
               DrawInventoryScreen
            Endif
         Endif
         'Команда — съесть/выпить.
         If kch = "E" Then
            ret = ProcessEatDrink()
            'Экран изменился, перерисуем.
            If ret = TRUE Then
               DrawInventoryScreen
            Endif
         Endif
         'Команда - выбросить.
         If kch = "D" Then
            ret = ProcessDrop()
            'Экран изменился, перерисуем.
            If ret = TRUE Then
               DrawInventoryScreen
            Endif
         Endif
         'Команда -= осмотреть.
         If kch = "I" Then
            ret = ProcessInspect()
            'Экран изменился, перерисуем.
            If ret = TRUE Then
               DrawInventoryScreen
            Endif
         Endif
         'Команда - снять.
         If kch = "U" Then
            ret = ProcessUnequip()
            'Экран изменился, перерисуем.
            If ret = TRUE Then
               DrawInventoryScreen
            Endif
         Endif
         'Команда — одеть/экипировать
         If kch = "Q" Then
            ret = ProcessEquip()
            'Экран изменился, перерисуем.
            If ret = TRUE Then
               DrawInventoryScreen
            Endif
         Endif
         'Команда - прочитать.
         If kch = "R" Then
            ret = ProcessRead()
            'Экран изменился, перерисуем.
            If ret = TRUE Then
               DrawInventoryScreen
            Endif
         Endif
      Endif
      Sleep 1
    Loop Until kch = key_esc
    ClearKeys
  End Sub

Подпрограмма ManageInventory() обрабатывает нажатые игроком клавиши и в зависимости от них, вызывает необходимую подпрограмму для различных команд, вызываемых в окне инвентаря. В нее мы добавили новую секцию, для обработки команды «Прочитать» при помощи вызова функции ProcessRead, в которой мы и будем читать наши магические книги.

dod.bas
'Обработка команды ПРОЧИТАТЬ.
  Function ProcessRead() As Integer
    Dim As String res, mask, desc
    Dim As Integer i, iret, iitem, ret = FALSE
    Dim As invtype inv
    Dim As tWidgets.btnID btn
    Dim As tWidgets.tInputbox ib
       
    'Переберем все предметы инвентаря.
    For i = pchar.LowInv To pchar.HighInv
      iitem = pchar.HasInvItem(i)
      If iitem = TRUE Then
         'Получим предмет.
         pchar.GetInventoryItem i, inv
         'Если предмет опознан и может быть прочитан.
         iret = MatchUse(inv, useRead)
         'Если подходит по условиям.
         If iret = TRUE Then
            'Построим маску.
            mask &= Chr(i)
         End If
      Endif
    Next
    If Len(mask) = 0 Then
      ShowMsg "Read", "Nothing to read.", tWidgets.MsgBoxType.gmbOK
    Else
      'Нарисуем поле для ввода.
      ib.Title = "Read"
      ib.Prompt = "Select item(s) to read (" & mask & ")"
      ib.Row = 39
      ib.EditMask = mask
      ib.MaxLen = Len(mask)
      ib.InputLen = Len(mask)
      btn = ib.Inputbox(res)
      'Проверим с каждым предметом из списка.
      If (btn <> tWidgets.btnID.gbnCancel) And (Len(res) > 0) Then
         ret = TRUE
         'Сравним с предметами.
         For i = 1 To Len(res)
            iitem = Asc(res, i) 'Get index into character inventory.
            'Получим предмет инвентаря.
            pchar.GetInventoryItem iitem, inv
            If IsEval(inv) = TRUE Then
               desc = pchar.ApplyInvItem(inv)
               ShowMsg "Read", desc, tWidgets.MsgBoxType.gmbOK   
               'Очистим предмет.
               ClearInv inv
               'Вернем в инвентарь.
               pchar.AddInvItem iitem, inv
            Else
               ShowMsg "Read", "Cannot read unevaluated spell books.", tWidgets.MsgBoxType.gmbOK
            End If
         Next
      Endif
    Endif
     
    Return ret
  End Function

Для чтения книг, мы следуем тоже схеме, что и для других команд работы с инвентарем. Вначале мы проверяем все предметы инвентаря на наличие флага useRead и строим список доступных вариантов выбора предмета для игрока.

После того, как игрок делает выбор мы вызывает подпрограмму объекта персонажа ApplyInvItem. Она была нами добавлена при добавлении в игру предметов питания. Эта же подпрограмма может быть нами использована и для чтения магических книг.

character.bi
'Применяет предмет из инвентаря на персонажа.
  Function character.ApplyInvItem(inv As invtype) As String
    Dim As Integer evalstate, evaldr, amt, amt2, amt3, chk = FALSE
    Dim As String ret
     
    'Опознан ли предмет.
    evalstate =  IsEval(inv)
    'Магический ли предмет
    evalDR = GetEvalDR(inv)
     
    If inv.classid = clSupplies Then
 
    ...
 
    Elseif inv.classid = clSpellBook Then
      'Проверим, рустая ли это книга.
      If inv.spellbook.id = bkSpellBlank Then
         ret = "The spell book is blank."
      Else
         'Вначале проверим, есть ли это заклинание в списке заученных.
         For i As Integer = 65 To 90
            If _cinfo.cspells(i).spell.id = inv.spellbook.spell.id Then
               'Увеличим уровень заклинания.
               _cinfo.cspells(i).spell.lvl += inv.spellbook.spell.lvl
               ret = "You gained a level in " & inv.spellbook.spell.splname & "!"
               chk = TRUE
               Exit For
            Endif
         Next
         'Не найдено в списке, нужно добавить.
         If chk = FALSE Then
            'Добавим в первый свободный слот.
            For i As Integer = 65 To 90
               If _cinfo.cspells(i).spell.id = splNone Then
                  _cinfo.cspells(i).classid = clSpell
                  _cinfo.cspells(i).spell.id = inv.spellbook.spell.id
                  _cinfo.cspells(i).spell = inv.spellbook.spell
                  ret = "You learned " & inv.spellbook.spell.splname & "!"
                  Exit For
               Endif
            Next
         Endif
      End If
    Endif
     
    Return ret
  End Function

Сначала мы проверяем — пустая ли это книга. Если пустая, то сообщаем об этом игроку и выходим из функции. Если же книга содержит в себе заклинание, то мы проверяем — знает его персонаж или нет. Обратите внимание, что мы используем индексы от 65 до 90, что соответствует кодам прописных букв английского алфавита от A до Z. Данную технику мы использовали для упрощения выбора предметов инвентаря. Ее мы используем и здесь.

Если заклинание уже содержится в списке выученных заклинаний, то мы просто увеличим его уровень при помощи команды: _cinfo.cspells(i).spell.lvl += inv.spellbook.spell.lvl. Это увеличит мощность конкретного заклинания. Для проверки, найде но ли заклинание, мы устанавливаем флаг chk = TRUE, который проверяем после выхода из первого цикла.

Но как насчет самого списка выученных заклинаний? Он содержится в объекте персонажа.

character.bi
'Атрибуты персонажа.
  Type characterinfo
    ...
    cinv(97 To 122) As invtype 'Инвентарь персонажа, использует ascii коды a-z для индексации.
    cspells(65 To 90) As invtype 'Выученные персонажем заклинания, используются ascii коды A-Z для индексации.
    ...
  End Type

Обратите внимание, что мы описали его также как и список инвентаря. В результате и работать он будет так же, как и инвентарь персонажа.

character.bi:ApplyInvItem
...
         ''Не найдено в списке, нужно добавить.
         If chk = FALSE Then
            'Добавим в первый свободный слот.
            For i As Integer = 65 To 90
               If _cinfo.cspells(i).spell.id = splNone Then
                  _cinfo.cspells(i).classid = clSpell
                  _cinfo.cspells(i).spell = inv.spellbook.spell
                  ret = "You learned " & inv.spellbook.spell.splname & "!"
                  Exit For
               Endif
            Next
         Endif
  ...

Если выполнение программы дошло до этих строк, то мы знаем, что заклинания нет в списке выученных персонажем заклинаний и нам необходимо его добавить. Первое что мы делаем, это ищем первый свободный слот. Когда слот найден, мы устанавливаем его идентификатор в clSpell и переносим информацию о заклинании из книги в слот выученного заклинания командой _cinfo.cspells(i).spell = inv.spellbook.spell. (примечание переводчика: тут, очевидно, нужно еще скопировать и уровень заклинания: _cinfo.cspells(i).spell.lvl = inv.spellbook.spell.lvl).

Последнее что мы рассмотрим, это определение типа предмета инвентаря, добавив возможность хранить в нем заклинания.

inv.bi
'Описание типа предмета инвентаря.
  Type invtype
    classid As classids     'Указывает, на тип предмета, содержащегося в объединении.
    desc As String * 30     'Текст с описанием.
    icon As String * 1      'Иконка предмета.
    iconclr As Uinteger     'Цвет иконки предмета.
    Union                   'Объединение, для предметов различных типов .
      gold As goldtype     'Золотые монеты.
      supply As supplytype 'Еда.
      armor As armortype   'Броня
      shield As shieldtype 'Шит
      weapon As weapontype 'Оружие
      ammo As ammotype     'Заряды для дистанционного оружия.
      potion As pottype    'Зелья.
      jewelry As jewelrytype 'Кольца и ожерелья
      spellbook As spellbktype 'Магическая книга.
      spell As spelltype     'Заклинания
    End Union
  End Type

Обратите внимание, что мы добавили в объединение с информацией о предмете инвентаря записи для заклинаний и магических книг. Разумеется необходимо добавить их в в список идентификаторов классов предметов.

inv.bi
'Идентификаторы классов предметов.
  Enum classids
    clNone
    clGold
    clSupplies
    clArmor
    clShield
    clWeapon
    clAmmo
    clPotion
    clRing
    clNecklace
    clSpellBook
    clSpell
  End Enum

Новое, что нам пришлось добавить для описания типа предмета для заклинаний, это короткое название заклинания.

inv.bi
'Информация о заклинании.
  Type spelltype
    id As spellid          'Идентификатор заклинания.
    lvl As Integer         'Уровень заклинания.
    splname As String * 30 'Название заклинания.
    splsname As String * 8 'Короткое название заклинания.
    spldesc As String * 60 'Описание заклинания.
    manacost As Integer    'Цена использования в единицах маны.
    dam As Integer         'Кол-во наносимых повреждений.
  End Type

Так как, в большинстве случаев, мы использовали базу уже написанного ранее кода, то нам не пришлось вносить много изменений для добавления в игру книг с заклинаниями. Причина в том, что у нас уже был готовый рабочий код для обработки предметов инвентаря, представив книги с заклинаниями и сами заклинания, как всего лишь, новые типы предметов, нам пришлось только внести инструкции для их обработки в уже существующие функции и процедуры, и это отлично работает! Я надеюсь что вы видите, что мы не зря потратили время на реализацию системы инвентаря, теперь она нам экономит времени значительно больше. Даже добавление новых заклинаний, теперь будет не более трудозатратно, чем добавление книг с заклинаниями, которые мы только что реализовали.

Теперь, когда наш персонаж может изучать заклинания, следующий шаг — научить его ими пользоваться.

Перевод на русский: Fantik

содержание | назад | вперед