Давайте сделаем рогалик (Магия брони)

armormagic.png

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

inv.bi

'ID эффектов.
  Enum spellid
    ...
    splNoSlash     'Броня: +% от рубящих атак.
    splNoCrush     'Броня: +% от дробящих атак.
    splNoPierce    'Броня: +% от колотых атак.
    splNoEnergy    'Броня: +% от энергетически атак.
    splNoFire      'Броня: +% от огненных атак.
    splNoAcid      'Броня: +% от кислотных атак.
    splNoMagic     'Броня: +% от магических атак.
  End Enum


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

inv.bi

'Типы наносимых орудием повреждений.
  Enum weapdamtype
    wdNone
    wdSlash
    wdCrush
    wdPierce
    wdEnergy
    wdFire
    wdAcid
    wdMagicProt
    wdMagic
  End Enum

В данном перечислении указаны типы различных атак, которые мы будем использовать для идентификации атаки. Мы также должны обновить названия и описания заклинаний для добавления заклинаний отражения определенных типов атаки. Новые названия и описания содержаться в GetSpellName и GetSpellEffect файла inv.bi. Далее следует процедура добавления заклинаний для брони и щитов.

inv.bi: GenerateShield, GenerateArmor

...
    If IsMagic = TRUE Then
      inv.shield.evaldr = GetScaledFactor(charint, currlevel) 'Значение.
      inv.shield.spell.id = RandomRange(splNoSlash, splNoMagic) 'ID заклинания.
      GenerateSpell inv.shield.spell, currlevel
      inv.shield.spelleffect = GetArmorSpellEffect(inv.shield.spell.id)
    Endif
  ...
   If IsMagic = TRUE Then
      inv.armor.evaldr = GetScaledFactor(charint, currlevel) 'Значение.
      inv.armor.spell.id = RandomRange(splNoSlash, splNoMagic) 'ID заклинания.
      inv.armor.spelleffect = GetArmorSpellEffect(inv.armor.spell.id)
      GenerateSpell inv.armor.spell, currlevel
    Endif
  ...


Здесь у нас код для щитов и брони. Он почти идентичен коду для оружия, новое здесь, это функция GetArmorSpellEffect.

inv.bi

'Возвращает магическое свойство брони.
  Function GetArmorSpellEffect(id As spellid) As weapdamtype
    Dim ret As weapdamtype
      
    Select Case id
      Case splNoSlash    
         ret = wdSlash             
      Case splNoCrush    
         ret = wdCrush
      Case splNoPierce   
         ret = wdPierce
      Case splNoEnergy   
         ret = wdEnergy
      Case splNoMagic    
         ret = wdMagicProt
      Case splNoFire     
         ret = wdFire
      Case wdAcid        
         ret = wdAcid
      Case Else
         ret = wdNone
    End Select
    
    Return ret
  End Function


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

inv.bi

'Типы оружия.
  Type weapontype
    ...
    weapontype As weaptype    'Тип оружия: melee, projectile.
    damtype As weapdamtype    'Тип наносимого повреждения.
    ...
  End Type


damtype — новое поле в описании типа оружия

inv.bi:GenerateWeapon

'Назначим параметры для различных типов оружия.
    Select Case item
      Case wpClub            '1 ручное, повр 4 символ для отображения:33
         inv.desc = "Club"
         inv.icon = Chr(33)
         inv.weapon.noise = 1
         inv.weapon.dam = 4
         inv.weapon.hands = 1
         inv.weapon.damtype = wdCrush
    ...

Это пример назначения различных типов атак для различных типов оружия. Дубина или булава будут наносить дробящие повреждения, меч — рубящие, а лук — колющие.

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

monster.bi
Type montype
  ...
    atkrange As Integer  'Дистанционные атаки.
    damtype As weapdamtype 'Тип атаки.
    damstr As String * 10 'Строка для отображения типа атаки.
  ...
  End Type

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

monster.bi
...
    'Назначим индивидуальные аттрибуты.
    Select Case mon.id
      Case monDarkangel
         mon.mname = "Dark Angel" 'Имя
         mon.micon = "A"          'Иконка на карте.
         mon.ismagic = TRUE 'Флаг магии.
         mon.spell.id = splNone      'Заклинание.
         mon.atkrange = 1 'Дальность атаки.
         mon.dropcount = 1  'Кол-во вещей в инвентаре.
         GenerateWeapon inv, currlevel, RandomRange(wpSmallsword, wpBishopsflail) 'Оружие.
         mon.dropitem(1) = inv 'Назначим предмет инвертаря.
         mon.atkdam = mon.dropitem(1).weapon.dam 'Повреждение оружием.
         mon.armval = .8 'Класс брони.
         mon.cf = acfsk 'Фактор атаки.
         mon.damtype = mon.dropitem(1).weapon.damtype 'Тип наносимого повреждения.
      Case monGiantbat
         mon.mname = "Giant Bat" 'Имя
         mon.micon = "B"          'Иконка на карте.
         mon.ismagic = FALSE 'Флаг магии.
         mon.spell.id = splNone      'Заклинание.
         mon.atkrange = 1 'Дальность атаки.
         mon.dropcount = 0  'Кол-во вещей в инвентаре.
         mon.atkdam = stratt / 4 'Сколько повреждений наносит.
         mon.armval = .1 'Класс брони.
         mon.damtype = wdPierce
    ...
 
    'Назначим строку для наносимых повреждений.
    Select Case mon.damtype
      Case wdSlash
         mon.damstr = "slash"
      Case wdCrush
         mon.damstr = "crush"
      Case wdPierce
         mon.damstr = "pierce"
      Case wdEnergy
         mon.damstr = "energy"
      Case wdFire
         mon.damstr = "fire"
      Case wdAcid
         mon.damstr = "acid"
      Case wdMagic
         mon.damstr = "magic"
      Case Else
         mon.damstr = ""
    End Select
  ...

Здесь показаны только два примера заполнения новых полей. Тип атаки Темного Ангела берется в соответствии с оружием, которое он держит в руках, в то время, как Гигантская летучая мышь владеет врожденной атакой, которую я классифицировал как колющую, предполагая что она при нападении использует свои клыки. Вторая часть кода просто проверяет поле damtype и, в соответствии с ним, заполняет строку описания для дальнейшего использования.

Теперь у нас есть различные типы атак, необходимо добавить их обработку в функцию применения брони.

character.bi

'Возвращает текущее значение защиты брони.
  Function character.GetArmorValue(wp As weapdamtype = wdNone) As Single
    Dim As Single ret = 0.0
    
   'Проверим броню.
   If _cinfo.cwield(wArmor).classid <> clNone Then
      ret = _cinfo.cwield(wArmor).armor.dampct
      'Убедимся что указан тип атаки.
      If wp <> wdNone Then
         'Проверим магические свойства.
         If _cinfo.cwield(wArmor).armor.eval = TRUE Then
            'Если наложено заклинане.
            If _cinfo.cwield(wArmor).armor.spell.id <> splNone Then
               'Эффект защиты совпадает с эффектом атаки.
               If _cinfo.cwield(wArmor).armor.spelleffect = wp Then
                  'Увеличим значение.
                  ret += (_cinfo.cwield(wArmor).armor.spell.lvl / 100)
               Endif
            Endif
         Endif
      End If
    Endif
    
    Return ret
  End Function
   
  'Возвращает полную защиту, добавляемую щитом.
  Function character.GetShieldArmorValue (wp As weapdamtype = wdNone) As Single
    Dim As Single ret = 0.0
    Dim As Integer cnt
       
    'Проверми все щиты.
    If _cinfo.cwield(wPrimary).classid = clShield Then
      ret += _cinfo.cwield(wPrimary).shield.dampct
      cnt = 1
      'Убедимся, что есть какой либо тип атаки.
      If wp <> wdNone Then
         'Проверим магические свойства.
         If _cinfo.cwield(wPrimary).shield.eval = TRUE Then
            'Если наложено заклинане.
            If _cinfo.cwield(wPrimary).shield.spell.id <> splNone Then
               'Эффект защиты совпадает с эффектом атаки.
               If _cinfo.cwield(wPrimary).shield.spelleffect = wp Then
                  'Увеличим значение.
                  ret += (_cinfo.cwield(wPrimary).shield.spell.lvl / 100)
                  cnt += 1
               Endif
            Endif
         Endif
      End If
    Endif  
    If _cinfo.cwield(wSecondary).classid = clShield Then
      ret += _cinfo.cwield(wSecondary).shield.dampct
      cnt += 1
      'Убедимся что указан тип атаки.
      If wp <> wdNone Then
         'Проверим магические щиты.
         If _cinfo.cwield(wSecondary).shield.eval = TRUE Then
            'Если наложено заклинане.
            If _cinfo.cwield(wSecondary).shield.spell.id <> splNone Then
               'Эффект защиты совпадает с эффектом атаки.
               If _cinfo.cwield(wSecondary).shield.spelleffect = wp Then
                  'Увеличим значение.
                  ret += (_cinfo.cwield(wSecondary).shield.spell.lvl / 100)
                  cnt += 1
               Endif
            Endif
         Endif
      End If
    Endif
    
    'Получим среднее значение.
    If cnt > 0 Then
      ret = ret / cnt
    Endif
    
    Return ret
  End Function

Мы обновили расчет брони и щитов, для дополнительной защиты, если на них наложены магические заклинания. Поскольку щит может быть как в левой, так и в правой руке (или обоих), то для щитов, мы должны проверить оба слота. В каждую функцию я добавил проверку нового параметра wp – тип наносимого оружием повреждения, который по умолчанию равен wdNone – что означает, что тип повреждения не указан, и, соответственно, магический эффект брони не влияет на такие повреждения.

Процесс вычисления не очень сложен. Вначале мы получаем базовае значение защиты, затем проверяем что назначен специальный тип наносимых повреждений (If wp <> wdNone). Далее нужно убедиться что броня или щиты опознаны (_cinfo.cwield(wArmor).armor.eval), так как, если нет, то магические свойства будут недоступны. После мы должны проверить, назначены ли какие либо заклинания (_cinfo.cwield(wArmor).armor.spell.id <> splNone) и совпадает ли тип защитного заклинания с типом атаки (_cinfo.cwield(wArmor).armor.spelleffect = wp). Если все это верно, то увеличиваем значение защиты, которое обеспечивает данная броня или щит.

Нам необходимо внести небольшое изменение в код монстра для применения данных эффектов.

map.bi

'Монстр атакует персонажа.
  Sub levelobj.MonsterAttack(mx As Integer, my As Integer)
    Dim As Integer midx, cd, mc, rollc, rollm, chp, dam
    Dim As String txt
    Dim As Single arm
    
    'Убедимся, что монстр находится здесь.
    If _level.lmap(mx, my).monidx > 0 Then
      midx = _level.lmap(mx, my).monidx
      'Получим файтор защиты персонажа.
      cd = pchar.GetDefenseFactor()
      'Получим фактор атаки монстра.
      mc = _level.moninfo(midx).cf
      'Возьмем случайные значения.
      rollc = RandomRange(1, cd)
      rollm = RandomRange(1, mc)
      'Сравним случайные значения монстра и персонажа.
      If rollm > rollc Then
         'Получим повреждения.
         dam = _level.moninfo(midx).atkdam
         'Получтм защиту щита, если есть.
         arm = pchar.GetShieldArmorValue(_level.moninfo(midx).damtype)
         'Если есть защита.
         If arm > 0 Then
            dam = dam - (dam * arm)
         End If
         'Получим защиту брони.
         arm = pchar.GetArmorValue (_level.moninfo(midx).damtype)
         'Если есть защита.
         If arm > 0 Then
            dam = dam - (dam * arm)
         End If
         If dam < 1 Then dam = 1
         'Получим здоровье персонажа
         chp = pchar.CurrHP
         'Отнимем здоровье.
         chp -= dam
         If chp < 0 Then chp = 0
         'Обновим здоровье персонажа.
         pchar.CurrHP = chp
         txt = "The " &  _level.moninfo(midx).mname & " hits for " & dam & " " & _level.moninfo(midx).damstr & " damage points."
      Else
         txt = "The " &  _level.moninfo(midx).mname & " misses."
      Endif
      PrintMessage txt
    End If  
  End Sub

Обратите внимание, теперь мы передаем тип атаки монстра в функции GetShieldArmorValue и GetArmorValue. Больше нам ничего не нужно делать, для добавления расчета магии брони. Значение строки с описанием атаки монстра, которые мы заполняли в процедуре генерации монстров, используется здесь для вывода игроку сообщения об атаке.

Последний шаг, который нам необходимо сделать, это обновить процедуры отображения предметов. Мы добавим индикатор «M» рядом с описанием предмета на основном экране и экране инвентаря, для того, чтобы сразу было видно, что данный предмет имеет магические свойства.

dod.bas

'Нарисуем главный экран игры.
  Sub DrawMainScreen (db As Integer = FALSE)
  ...
    'Проверим экипированные предметы.
    If pchar.HasInvItem(wPrimary) = TRUE Then
      pchar.GetInventoryItem wPrimary, inv
      idesc = GetInvItemDesc(inv)
      spl = pchar.GetItemSpell(wPrimary)
      If spl.id <> splNone Then
         idesc &= " (M)"
      Endif
    Else
      idesc = ""
    Endif
    PutText "Primary: " & idesc, row, col
  ...

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

dod.bas

'Отобразим инвертарь персонажа.
  Sub DrawInventoryScreen()
  ...
         'Получим предмет инвентаря.
         pchar.GetInventoryItem i, inv
         'Опознан ли предмет.
         ret = IsEval(inv)
         'Получим описание.
         desc = GetInvItemDesc(inv)
         'Увет предмета.
         clr = inv.iconclr
         'Построим строку с описанием.
         txt = Chr(i) & " " & desc & " "
         'Если не опознан, то дадим игроку знать, что предмет можно опознать.
         If ret = FALSE Then
            txt &= "(*)"
         Else
            spl = pchar.GetItemSpell(inv)
            If spl.id <> splNone Then
               txt &= " (M)"
            Endif
         Endif
      Else
  ...

Мы перегрузили функцию  GetItemSpell для того, чтобы она принимала предмет инвентаря. Ее код остался прежним, просто вместо предмета из слота мы проверяем предмет инвентаря.

character.bi

'Возвращает информацию о заклинании.
  Function character.GetItemSpell(inv As invtype) As spelltype
    Dim ret As spelltype
    
    'Очистим тип заклинания.
    ClearSpell ret
          
    'Проверим, оружие ли это.
    If inv.classid = clWeapon Then
      'Проверим, опознано или нет.
      If inv.weapon.eval = TRUE Then
         'Возвращаем заклинание; может быть splNone.
         ret = inv.weapon.spell
      End If
    Elseif inv.classid = clShield Then
      'Проверим, опознан ли щит.
      If inv.shield.eval = TRUE Then
         'Возвращаем заклинание; может быть splNone.
         ret = inv.shield.spell
      End If
    Elseif inv.classid = clArmor Then
      'Опознана ли броня.
      If inv.armor.eval = TRUE Then
         'Возвращаем заклинание; может быть splNone.
         ret = inv.armor.spell
      End If
    Elseif inv.classid = clRing Then
      'Опознано ли кольцо.
      If inv.jewelry.eval = TRUE Then
         'Возвращаем заклинание; может быть splNone.
         ret = inv.jewelry.spell
      End If
    Elseif inv.classid = clNecklace Then
      'Опознан ли кулон.
      If inv.jewelry.eval = TRUE Then
         'Возвращаем заклинание; может быть splNone.
         ret = inv.jewelry.spell
      End If
    End If
     
    Return ret
  End Function

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

Этого достаточно, чтобы в нашей игре появились магические броня и щиты. Следующим наши шагом будет добавление в игру магических украшений.

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

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