Давайте сделаем рогалик (Магия брони)
Как и оружие, доспехи и щиты также могут иметь магические эффекты. Для начала давайте посмотрим на обновленный список заклинаний.
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.biType 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
содержание | назад | вперед