суббота, 21 июля 2012 г.

Давайте сделаем рогалик. Глава 26: Зелья


Зелья являются одними из основных составляющих любой РПГ игры, и было бы непростительно, если бы мы не включили их Подземелье Судьбы. Есть несколько способов, которыми вы можете реализовать зелья, использовать их как дополнительный магический механизм или влиять ими на характеристики персонажа. Перед употреблением, персонажу имеет смысл изучить эффекты, которые оказывает то или иное зелье. В нашем Подземелье Судьбы зелья будут изменять атрибуты персонажа.В нашей ролевой системе есть 2 типа атрибутов, это базовые атрибуты — такие как сила или выносливость, и рассчитываемые боевые факторы, зависящие от них. В результате зелья у нас будут влиять на разные типы характеристик по разному. Для базовых типов параметров персонажа эффект зелья будет постоянным. Если персонаж выпьет «Зелье силы», то его сила увеличится перманентно. Эффект от зелий, влияющих на боевые атрибуты будет длиться временно — определенное количество ходов. Я думаю, что это будет хорошо работать с созданной нами системой характеристик персонажа.


Помните, что у нас может быть только один активный эффект для боевых факторов в один и тот же момент времени. Если на персонажа действует эффект +10 к фактору ближнего боя на 20 ходов и он выпьет зелье «Вооруженный Бой», которое дает +5 к фактору ближнего боя на 10 ходов, то эффект наносимый последним выпитым зельем заменит предыдущий. Это позволит избежать накопления эффектов и предотвратит внесение дисбаланса в игру.


С основными правилами разберемся на месте. Давайте рассмотрим следующий код.


inv.bi
'Эффект от применения зелий.
 Enum poteffect
   potEffectNone
   potStrength
   potStamina
   potDexterity
   potAgility
   potIntelligence
   potUCF
   potACF
   potPCF
   potMCF
   potCDF
   potMDF
   potHealing
   potMana
 End Enum

 'Идентификаторы зелий.
 Enum potionids
   potNone
   potWhite 'str
   potBlack 'sta
   potBlue 'dex
   potGreen 'agl
   potCyan 'int
   potRed 'ucf
   potMagenta 'acf
   potYellow 'pcf
   potGray 'mcf
   potSilver 'cdf
   potGold 'mdf
   potOrange 'healing
   potPink 'mana
 End Enum

 'Типы зелий.
 Type pottype
   id As potionids
   potname As String * 30 'Название зелья. Скрыто до распознания.
   evaldr As Integer    'Сложность распознания. Используется для определения, машический ли предмет: 0 = не магический.
   eval As Integer      'Истина, если опознан.
   noise As Integer     'Количество генерируемого шума.
   use As itemuse       'Как используется.
   amt As Integer       'Сила эффекта.
   cnt As Integer       'Продолжительность эффекта.
   effect As poteffect  'Тип эффекта.
 End Type

Здесь у нас 2 перечисления, poteffect — в котором перечислены эффекты, и potionids, содержащий список самих зелий. Использование цвета в названии зелья является одним из правил их именования в рогаликах, поэтому мы также будем его использовать. Тип pottype описывает структуру данных для зелий. Она идентична структурам данных для других предметов. Поле effect содержит один из перечисленных выше эффектов, которое мы добавили для удобства применения эффекта при употреблении зелья.


inv.bi
'Создание зелий.
 Sub GeneratePotion(inv As invtype, currlevel As Integer, potid As potionids = potNone)
   Dim item As potionids 
   Dim As Integer effmax = 100
   item = potid
   'Генерируем предмет, если не задан.
   If item = potNone Then
      item = RandomRange(potWhite, potPink)
   EndIf
   'Общие для всех зелий параметры.
   inv.classid = clPotion
   inv.potion.id = item
   inv.potion.use = useEatDrink
   inv.potion.eval = FALSE
   inv.potion.evaldr = RandomRange(currlevel, currlevel * 2)
   inv.icon = Chr(147)
   inv.potion.noise = 10
   inv.potion.amt = RandomRange(-1, 10)
   If inv.potion.amt = 0 Then inv.potion.amt = 1 
   inv.potion.cnt = 0
   'Заданим описание, эффект и иконку.
   Select Case item
      Case potWhite 'сила
         inv.iconclr = fbWhite
         inv.potion.potname = "Potion of Strength"
         inv.desc = "White Potion"
         inv.potion.effect = potStrength
      Case potBlack 'выностивость
         inv.iconclr = fbBlack
         inv.potion.potname = "Potion of Stamina"
         inv.desc = "Black Potion"
         inv.potion.effect = potStamina
      Case potBlue 'ловкость
         inv.iconclr = fbBlue
         inv.potion.potname = "Potion of Dexterity"
         inv.desc = "Blue Potion"
         inv.potion.effect = potDexterity
      Case potGreen 'подвижность
         inv.iconclr = fbGreen
         inv.potion.potname = "Potion of Agility"
         inv.desc = "Green Potion"
         inv.potion.effect = potAgility
      Case potCyan 'интеллект
         inv.iconclr = fbCyan
         inv.potion.potname = "Potion of Intelligence"
         inv.desc = "Cyan Potion"
         inv.potion.effect = potIntelligence
      Case potRed 'безоружный бой
         inv.iconclr = fbRed
         inv.potion.potname = "Potion of Unarmed Comabt"
         inv.desc = "Red Potion"
         inv.potion.cnt = RandomRange(1, effmax)
         inv.potion.effect = potUCF
      Case potMagenta 'ближний бой
         inv.iconclr = fbMagenta
         inv.potion.potname = "Potion of Armed Combat"
         inv.desc = "Magenta Potion"
         inv.potion.cnt = RandomRange(1, effmax)
         inv.potion.effect = potACF
      Case potYellow 'дистанционная атака
         inv.iconclr = fbYellow
         inv.potion.potname = "Potion of Projectile Combat"
         inv.desc = "Yellow Potion"
         inv.potion.cnt = RandomRange(1, effmax)
         inv.potion.effect = potPCF
      Case potGray 'магическая атака
         inv.iconclr = fbGray
         inv.potion.potname = "Potion of Magic Comabt"
         inv.desc = "Gray Potion"
         inv.potion.cnt = RandomRange(1, effmax)
         inv.potion.effect = potMCF
      Case potSilver 'физическая защита
         inv.iconclr = fbSilver
         inv.potion.potname = "Potion of Combat Defense"
         inv.desc = "Silver Potion"
         inv.potion.cnt = RandomRange(1, effmax)
         inv.potion.effect = potCDF
      Case potGold 'магическая защита
         inv.iconclr = fbGold
         inv.potion.potname = "Potion of Magic Defense"
         inv.desc = "Gold Potion"
         inv.potion.cnt = RandomRange(1, effmax)
         inv.potion.effect = potMDF
      Case potOrange 'лечение
         inv.iconclr = fbOrange
         inv.potion.potname = "Potion of Healing"
         inv.desc = "Orange Potion"
         inv.potion.effect = potHealing
      Case potPink 'мана
         inv.iconclr = fbPink
         inv.potion.potname = "Potion of Mana"
         inv.desc = "Pink Potion"
         inv.potion.effect = potMana
      Case Else
         inv.iconclr = fbWhite
         inv.icon = "?"
         inv.potion.potname = "Unknown Potion"
         inv.desc = inv.potion.potname
         inv.potion.effect = potEffectNone 
   End Select   
 End Sub

Здесь все кажется знакомым, так как идентично генерации остальных предметов, однако есть одна особенность. Это значение поля amt (сила эффекта). Задаваемый диапазон включает в себя небольшую отрицательную величину и может принимать значение от -1 (и 0) до 10. Значение -1 кажется небольшим, но может сыграть большую роль в критический момент игры. Эффекты зелий применяются независимо от того, опознано оно или нет, так что возможность небольшого отрицательного эффекта заставит игрока задуматься перед его использованием. Это добавляет элемент случайности в игру, наряду с некоторым интересом. Позже, возможно, нам понадобиться вернуться к этому коду и поправить значение силы эффекта, но пока оставим все как и есть.


Другое поле в описании зелья, вызывающее интерес, это поде desc, содержащее описание предмета. Зелья являются «магическими предметами», поэтому фактические названия зелий, такие как «Зелье маны», не раскрываются до его опознания, поэтому мы будем отображать в инвентаре только цвет зелья, а истинное название игрок увидит только после опознания.


Рассмотрим изменения в подпрограмме SetInvEval.


inv.bi
'Устанавливает параметр eval для предмета inv. (определение предмета)
 Sub SetInvEval(inv As invtype, state As Integer)
   
   'Если нечего определять.
   If inv.classid <> clNone Then
      'Выберем предмет.
      Select Case inv.classid
         Case clSupplies
            inv.supply.eval = state 'Установим значение.
         Case clArmor
            inv.armor.eval = state 
         Case clShield
            inv.shield.eval = state 
         Case clWeapon
            inv.weapon.eval = state 
         Case clAmmo
            inv.ammo.eval = TRUE
         Case clPotion
            inv.potion.eval = state
            'установим истинное название зелья.
            If state = TRUE Then
               inv.desc = inv.potion.potname
            EndIf
      End Select
   EndIf
 End Sub

Когда устанавливается состояние True, то мы переходим к «тайному» описанию зелья, которое содержится в параметре potname.


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


Зелья являются расходным материалом, так что нам нужно еще обновить команду «Съесть/Выпить» основной программы. Давайте рассмотрим внесенные изменения.


dod.bas
'Обработка команды «Съесть/Выпить».
 Function ProcessEatDrink() 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, useEatDrink)
         'Если соответствует.
         If iret = TRUE Then
            'Построим маску.
            mask &= Chr(i)
         End If
      EndIf
   Next
   If Len(mask) = 0 Then
      ShowMsg "Eat/Drink", "Nothing to consume.", tWidgets.MsgBoxType.gmbOK
   Else
      'Draws an input box on screen.
      ib.Title = "Eat/Drink"
      ib.Prompt = "Select item(s) to eat/drink (" & 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
            desc = pchar.ApplyInvItem(inv) 
            ShowMsg "Eat/Drink", desc, tWidgets.MsgBoxType.gmbOK    
            'Очистим предмет.
            ClearInv inv
            'Поместим назад в инвентарь.
            pchar.AddInvItem iitem, inv
         Next
      EndIf
   EndIf
   
   Return ret
 End Function

Если вы помните, ранее в данное процедуре мы обрабатывали эффект предмета, теперь же применение эффекта перенесено в новый метод ApplyInvItem объекта персонажа.


character.bi
'Применить предмет из инвентаря на персонажа.
 Function character.ApplyInvItem(inv As invtype) As String
   Dim As Integer evalstate, evaldr, amt, amt2, amt3
   Dim As String ret
   
   'опознан ли предмет.
   evalstate = IsEval(inv)
   'проверка для магических предметов.
   evalDR = GetEvalDR(inv)
   
   If inv.classid = clSupplies Then
      'Применяем предмет на персонажа.
      If inv.supply.id = supHealingHerb Then
         'Опознанный машический предмет.
         If (evalstate = TRUE) And (evalDR > 0) Then
            CurrHP = MaxHP
            ret = "The Healing Herb completely healed you!"
         Else
            'лечим 50% здоровья .
            amt = MaxHP * .5 'Рассчитаем значение.
            CurrHP = CurrHP + amt
            If CurrHP > MaxHP Then
               CurrHP = MaxHP
            EndIf
            ret = "The Healing Herb added " & amt & " health!"
         EndIf
      ElseIf inv.supply.id = supHunkMeat Then
        'лечим 25% здоровья.
        amt = MaxHP * .25
         CurrHP = CurrHP + amt 
         If CurrHP > MaxHP Then
            CurrHP = MaxHP
         EndIf
         ret = "The Hunk of Meat added " & amt & " health!"
         'Опознанный магический предмет.
         If (evalstate = TRUE) And (evalDR > 0) Then
            amt2 = RandomRange(1, CurrStr)
            BonStr = amt2
            amt3 = RandomRange(1, 100)
            BonStrCnt = amt3  
            ret = "The Hunk of Meat added " & amt & " health and added " & amt2 & " strength for " & amt3 & " turns!" 
         EndIf
      ElseIf inv.supply.id = supBread Then
        'Лечим 10% здоровья.
        amt = MaxHP * .1
         CurrHP = CurrHP + amt 
         If CurrHP > MaxHP Then
            CurrHP = MaxHP
         EndIf
         ret = "The Bread added " & amt & " health!"
         'Опознанный магический предмет.
         If (evalstate = TRUE) And (evalDR > 0) Then
            'Вылечим отравление, если необходимо.
            If Poisoned = TRUE Then
               Poisoned = FALSE
               PoisonStr = 0
               ret = "The Bread added " & amt & " health and cured your poison!"
            End If
         EndIf
      ElseIf inv.supply.id = supManaOrb Then
         'Добавим 25% маны.
         amt = MaxMana * .25
         CurrMana = CurrMana + amt 
         If CurrMana > MaxMana Then
            CurrMana = MaxMana
         EndIf
         ret = "The Mana Orb added " & amt & " mana!"
         'Опознанный магический предмет.
         If (evalstate = TRUE) And (evalDR > 0) Then
            'Вылечим отравление, если необходимо.
            If CurrMana = MaxMana Then
               ret = "The Mana Orb restored all your mana!"
            End If
         EndIf
      EndIf
   ElseIf inv.classid = clPotion Then
      Select Case inv.potion.effect
         Case potStrength
            ChangeStrength inv.potion.amt
            If inv.potion.amt > 0 Then
               ret = "You gained " & inv.potion.amt & " strength!" 
            Else
               ret = "You lost " & inv.potion.amt & " strength!"
            EndIf
         Case potStamina
            ChangeStamina inv.potion.amt
            If inv.potion.amt > 0 Then
               ret = "You gained " & inv.potion.amt & " stamina!" 
            Else
               ret = "You lost " & inv.potion.amt & " stamina!"
            EndIf
         Case potDexterity
            ChangeDexterity inv.potion.amt
            If inv.potion.amt > 0 Then
               ret = "You gained " & inv.potion.amt & " dexterity!" 
            Else
               ret = "You lost " & inv.potion.amt & " dexterity!"
            EndIf
         Case potAgility
            ChangeAgility inv.potion.amt
            If inv.potion.amt > 0 Then
               ret = "You gained " & inv.potion.amt & " agility!" 
            Else
               ret = "You lost " & inv.potion.amt & " agility!"
            EndIf
         Case potIntelligence
            ChangeIntelligence inv.potion.amt
            If inv.potion.amt > 0 Then
               ret = "You gained " & inv.potion.amt & " intelligence!" 
            Else
               ret = "You lost " & inv.potion.amt & " intelligence!"
            EndIf
         Case potUCF
            BonUcf = inv.potion.amt
            BonUcfCnt = inv.potion.cnt
            If inv.potion.amt > 0 Then
               ret = "You gained " & inv.potion.amt & " Unarmed Combat for " & inv.potion.cnt & " turns!"
            Else
               ret = "You lost " & inv.potion.amt & " Unarmed Combat for " & inv.potion.cnt & " turns!"
            EndIf
         Case potACF
            BonAcf = inv.potion.amt
            BonAcfCnt = inv.potion.cnt
            If inv.potion.amt > 0 Then
               ret = "You gained " & inv.potion.amt & " Armed Combat for " & inv.potion.cnt & " turns!"
            Else
               ret = "You lost " & inv.potion.amt & " Armed Combat for " & inv.potion.cnt & " turns!"
            EndIf
         Case potPCF
            BonPcf = inv.potion.amt
            BonPcfCnt = inv.potion.cnt
            If inv.potion.amt > 0 Then
               ret = "You gained " & inv.potion.amt & " Projectile Combat for " & inv.potion.cnt & " turns!"
            Else
               ret = "You lost " & inv.potion.amt & " Projectile Combat for " & inv.potion.cnt & " turns!"
            EndIf
         Case potMCF
            BonMcf = inv.potion.amt
            BonMcfCnt = inv.potion.cnt
            If inv.potion.amt > 0 Then
               ret = "You gained " & inv.potion.amt & " Magic Combat for " & inv.potion.cnt & " turns!"
            Else
               ret = "You lost " & inv.potion.amt & " Magic Combat for " & inv.potion.cnt & " turns!"
            EndIf
         Case potCDF
            BonCdf = inv.potion.amt
            BonCdfCnt = inv.potion.cnt
            If inv.potion.amt > 0 Then
               ret = "You gained " & inv.potion.amt & " Combat Defense for " & inv.potion.cnt & " turns!"
            Else
               ret = "You lost " & inv.potion.amt & " Combat Defense for " & inv.potion.cnt & " turns!"
            EndIf
         Case potMDF
            BonMdf = inv.potion.amt
            BonMdfCnt = inv.potion.cnt
            If inv.potion.amt > 0 Then
               ret = "You gained " & inv.potion.amt & " Magic Defense for " & inv.potion.cnt & " turns!"
            Else
               ret = "You lost " & inv.potion.amt & " Magic Defense for " & inv.potion.cnt & " turns!"
            EndIf
         Case potHealing
            CurrHP = CurrHP + inv.potion.amt
            If CurrHP > MaxHP Then CurrHP = MaxHP 
            If inv.potion.amt > 0 Then
               ret = "You gained " & inv.potion.amt & " health!"
            Else
               ret = "You lost " & inv.potion.amt & " health!"
            EndIf
         Case potMana
            CurrMana = CurrMana + inv.potion.amt
            If CurrMana > MaxMana Then CurrMana = MaxMana
            If inv.potion.amt > 0 Then
               ret = "You gained " & inv.potion.amt & " mana!"
            Else
               ret = "You lost " & inv.potion.amt & " mana!"
            EndIf
      End Select
   EndIf
   
   Return ret
 End Function

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


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


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

Комментариев нет:

Отправить комментарий