Давайте сделаем рогалик (Магия оружия)
Теперь мы готовы приступить к реализации магической системы, и начнем мы с магии оружия. Магия оружия, это заклинание, которое привязано к оружию и используется во время успешного поражении цели. Для того, чтобы заклинание было активно, оружие должно быть опознано персонажем. Это звучит достаточно просто, и, как мы увидим, потребует не так много кода, чтобы все работало так, как нам надо.
inv.bi
'Идентификаторы эффектов. Enum spellid splNone 'Нет эффекта. splMaxHealing 'Восстанавливает здоровье до максимума. splStrongMeat 'Добавляет временный бонус к силе. splBreadLife 'Лечит отравление. splMaxMana 'Полностью восстанавливает ману. splSerpentBite 'Оружие: Наносит урон ядом. splRend 'Оружие: Уменьшает броню цели. splSunder 'Оружие: Снижает наносимый целью урон оружием. splReaper 'Оружие: Вызывает у монстра приступ страха. splFire 'Оружие: Поджигает цель. splGoliath 'Оружие: Добавляет к атаке дополнительную силу. splStun 'Оружие: Обездвиживает цель на время. splChaos 'Оружие: Добавляет случайные повреждения. splWraith 'Оружие: Уменьшает случайную характеристику цели. splThief 'Оружие: Забирает случайное количество от параметра цели и передает персонажу. End Enum
Мы добавили несколько новых идентификаторов заклинаний к перечислению
spellid. Первые пять вы уже видели, это заклинания расходных материалов,
остальные — заклинания для оружия. Разместив заклинания для оружия друг за
другом, мы сожем выбирать из списка случайное заклинание именно для оружия,
указав первое и последнее заклинание в качестве параметров выбора. Таким образом
мы в одно перечисление можем поместить все типы заклинаний. встречающихся в
нашей игре, и нам не нужно будет отслеживать несколько разных перечислений.
inv.bi
'Определение типа информации о заклинании. Type spelltype id As spellid 'Идентификатор заклинания. lvl As Integer 'Уровень заклинания. splname As String * 30 'Название заклинания. spldesc As String * 60 'Описание заклинания. manacost As Integer 'Стоимость маны. dam As Integer 'Кол-во возможных повреждений, наносимых заклинанием. End Type
Как вы помните, для предметов мы использовали только один цифровой
идентификатор заклинания. Однако, то что нам необходимо сделать, на самом деле,
это создать общую структуру данных описывающую все заклинания, а не жестко
привязать какой либо эффект к определенному предмету. Опять же, мы не хотим
иметь кучу разных структур данных для разных типов заклинаний, нам нужна одна
общая структура, что облегчит нам работу. Определение типа spelltype содержит
все необходимые поля данных, которые нам могут понадобиться для заклинаний
предметов, и заклинаний которые могут быть «вызваны» персонажем. Данная
структура поможет нам написать подпрограммы для работы с заклинаниями, которые
не зависят от типа самого заклинания.
Поле id содержит идентификатор
заклинания из перечисления spellid. Поле lvl содержит уровень заклинания. Чем
выше уровень заклинания, тем сильнее его эффект. Поля splname и spldesc
используются подпрограммой работы с инвентарем персонажа и содержат название
заклинания и его описание. Поле manacost указывает на количество маны,
необходимой для использования данного заклинания. Мы не будем использовать
данное поле для заклинаний оружия, так как данный тип заклинаний — это часть
самого оружия и не требует чего либо еще, однако количество маны будет играть
значительную роль при использовании заклинаний самим персонажем. Поле dam
содержит количество урона, наносимого данным заклинанием, но, так как не все
заклинания наносят урон, то мы будем использовать это поле и для других
целей.
Так как теперь мы используем определение типа для заклинаний, а не
целочисленное значение, то нам необходимо обновить определение типа
оружия.
inv.bi
'Определение типа для оружия. Type weapontype id As weaponids 'Тип оружия evaldr As Integer 'Сложность определения. Evaldr > 0 для маг. предметов. eval As Integer 'Определен ли предмет. spell As spelltype 'Заклинание. noise As Integer 'Кол-во шума при использовании или переноске. use As itemuse 'Как используется. dam As Integer 'Наносимые повреждения. hands As Integer 'Кол-во необходимых рук для использования. wslot(1 To 2) As wieldpos 'Какой слот занимает. Может занимать 2 слота. weapontype As weaptype 'Тип оружия: ближний бой, дистанционное. capacity As Integer 'Емкость обоймы. ammotype As ammoids 'Тип используемых боеприпасов. ammocnt As Integer 'Кол-во снарядов в обойме. iswand As Integer 'Если это «волшебная палочка». End Type
Единственное изменение заключается в том, что мы изменили тип поля spell на
spelltype вместо spellid. Когда мы создаем новое оружие, нам необходимо
заполнить данное поле данными, которые описывают прикрепленное к данному оружию
заклинание. Также, мы должны добавить обработку данного поля для всех
подпрограмм работы с оружием, так что давайте начнем, пожалуй, с подпрограммы
создания нового предмета оружия.
inv.bi
'Создание оружия. Sub GenerateWeapon(inv As invtype, currlevel As Integer, wpid As weaponids = wpNone) Dim item As weaponids Dim As Integer isMagic isMagic = ItemIsMagic(currlevel) item = wpid 'Создадим тип, если не задан. If item = wpNone Then item = RandomRange(wpClub, wpGoldWand) Endif 'Общее для всех предметов. inv.classid = clWeapon inv.weapon.id = item inv.iconclr = fbCadmiumYellow inv.weapon.use = useWieldWear inv.weapon.eval = FALSE inv.weapon.wslot(1) = wPrimary inv.weapon.wslot(2) = wSecondary inv.weapon.iswand = FALSE ClearSpell inv.weapon.spell 'Магический предмет. If IsMagic = TRUE Then inv.weapon.evaldr = GetScaledFactor(charint, currlevel) 'Сложность опознания. inv.weapon.spell.id = RandomRange(splSerpentBite, splThief) 'Идентификатор заклинания. GenerateSpell inv.weapon.spell, currlevel Endif 'Зададим значения по умолчанию для типа оружия и боеприпасов. inv.weapon.weapontype = wtMelee inv.weapon.ammotype = amNone inv.weapon.ammocnt = 0 'Зададим тип оружия и количество. Select Case item ...
Как видите, нам пришлось совсем немного ее изменить для поддержки нового типа
заклинаний. Хотя мы и добавили несколько новых подпрограмм. Подпрограмма
ClearSpell просто очищает структуру данных заклинания. GenerateSpell заполняет
структуру данных заклинания в соответствии с типом заклинания выбранного при
помощи функции RandomRange. Здесь вы можете увидеть, почему мы хотим, чтобы
идентификаторы заклинаний для оружия находились в одном месте. При добавлении
нового заклинания, мы добавим его идентификатор между существующими
идентификаторами задающими диапазон заклинаний, что позволит нам обойтись без
изменения существующего кода в GenerateWeapon. Необходимо будет только добавить
код для нового заклинания в процедуру GenerateSpell. Теперь давайте посмотрим на
две новых подпрограммы для создания заклинаний.
inv.bi
'Очийает структуру заклиания Sub ClearSpell (spl As spelltype) 'Clear the spell type. spl.id = splNone spl.lvl = 0 spl.splname = "" spl.spldesc = "" spl.manacost = 0 spl.dam = 0 End Sub
Процедура ClearSpell просто очищает структуру spelltype. Обратите внимание,
что мы передаем в процедуру именно только те данные, которые необходимо
очистить, а не всю структуру данных оружия. Это позволит нам использовать данную
процедуру не только для оружия, но и для заклинаний других предметов, в также
позволит избежать случайных изменений данных, которая данная процедура изменять
не должна.
inv.bi
'Заполняем данные заклинаний. Sub GenerateSpell(spl As spelltype, currlevel As Integer) 'Создаем заклинание основывая на его идентификаторе. Select Case spl.id Case splSerpentBite 'Оружие: Наносит урон ядом. spl.lvl = currlevel 'Current level is used for spell level. spl.splname = GetSpellName(spl.id) 'Sets the spell name. spl.spldesc = GetSpellEffect(spl.id) 'Sets the spell description. spl.manacost = 0 'Cost in mana. spl.dam = currlevel 'How much damage the spell does. Case splRend 'Оружие: Уменьшает броню цели. spl.lvl = currlevel spl.splname = GetSpellName(spl.id) spl.spldesc = GetSpellEffect(spl.id) spl.manacost = 0 spl.dam = currlevel Case splSunder 'Оружие: Снижает наносимый целью урон оружием. spl.lvl = currlevel spl.splname = GetSpellName(spl.id) spl.spldesc = GetSpellEffect(spl.id) spl.manacost = 0 spl.dam = currlevel Case splReaper 'Оружие: Вызывает у монстра приступ страха. spl.lvl = currlevel spl.splname = GetSpellName(spl.id) spl.spldesc = GetSpellEffect(spl.id) spl.manacost = 0 spl.dam = currlevel Case splFire 'Оружие: Поджигает цель. spl.lvl = currlevel spl.splname = GetSpellName(spl.id) spl.spldesc = GetSpellEffect(spl.id) spl.manacost = 0 spl.dam = currlevel Case splGoliath 'Оружие: Добавляет к атаке дополнительную силу. spl.lvl = currlevel spl.splname = GetSpellName(spl.id) spl.spldesc = GetSpellEffect(spl.id) spl.manacost = 0 spl.dam = currlevel Case splStun 'Оружие: Обездвиживает цель на время. spl.lvl = currlevel spl.splname = GetSpellName(spl.id) spl.spldesc = GetSpellEffect(spl.id) spl.manacost = 0 spl.dam = currlevel Case splChaos 'Оружие: Добавляет случайные повреждения. spl.lvl = currlevel spl.splname = GetSpellName(spl.id) spl.spldesc = GetSpellEffect(spl.id) spl.manacost = 0 spl.dam = RandomRange(1, currlevel) Case splWraith 'Оружие: Уменьшает случайную характеристику цели. spl.lvl = currlevel spl.splname = GetSpellName(spl.id) spl.spldesc = GetSpellEffect(spl.id) spl.manacost = 0 spl.dam = currlevel Case splThief 'Оружие: Забирает случайное количество от параметра цели и передает персонажу. spl.lvl = currlevel spl.splname = GetSpellName(spl.id) spl.spldesc = GetSpellEffect(spl.id) spl.manacost = 0 spl.dam = currlevel Case Else spl.lvl = 0 spl.splname = "Unknown spell" spl.spldesc = "Unknown spell effect." spl.manacost = 0 'Cost in mana. spl.dam = 0 End Select End Sub
Подпрограмма заполняет структуру данных заклинания основываясь на его
идентификаторе и текущей глубины подземелья. Глубина используется для задания
силы заклинания. По мере спуска персонажа на более глубокие уровни подземелья,
находимые им магические предметы будут становиться более сильными, предлагая
игроку больше тактических решений в игре. У игрока появится выбор, оставить
более сильное оружие со слабым заклинанием, или заменить его на более слабое, но
с более сильными магическими эффектами. Возможность выбора делает игру более
интересной и позволяет игроку пробовать разные варианты при прохождении
игры.
Как видите, формат этой процедуры точно такой же, как и других
процедур генерации. Я уже говорил это раньше, но стоит повторить еще раз:
создание однотипного кода упрощает его, облегчает его поддержку и делает его
самодокументируемым. Вернувшись к данной подпрограмме через месяцы, вы всегда
сможете понять что она делает, так как все подпрограммы у вас написаны в одном
стиле, что позволит вам добавлять новые предметы просто расширив шаблон. Это
делает программу более надежной и позволяет избежать случайных ошибок при
внесении изменений.
GenerateSpell довольно прост, он просто устанавливает
базовую информацию для заклинаний. Здесь у нас появляются две новых функции,
которые задают заклинаниям названия и описания. Сейчас мы их
рассмотрим.
inv.bi
'Возвращает название заклинания. Function GetSpellName(spl As spellid) As String Dim As String ret = "" Select Case spl Case splMaxHealing 'Восстанавливает здоровье до максимума. ret = "Spell of Maximum healing." Case splStrongMeat 'Добавляет временный бонус к силе. ret = "Spell of Enhance Strength." Case splBreadLife 'Лечит отравление. ret = "Spell of Cure Poison." Case splMaxMana 'Полностью восстанавливает ману. ret = "Spell of Restore Mana." Case splSerpentBite 'Оружие: Наносит урон ядом. ret = "Serpent's Bite" Case splRend 'Оружие: Уменьшает броню цели. ret = "Spell of Rend Armor" Case splSunder 'Оружие: Снижает наносимый целью урон оружием. ret = "Spell of Sunder" Case splReaper 'Оружие: Вызывает у монстра приступ страха. ret = "Spell of The Reaper" Case splFire 'Оружие: Поджигает цель. ret = "Spell of Fire" Case splGoliath 'Оружие: Добавляет к атаке дополнительную силу. ret = "The Strength of Goliath" Case splStun 'Оружие: Обездвиживает цель на время. ret = "Spell of Stun" Case splChaos 'Оружие: Добавляет случайные повреждения. ret = "Chaos Attack" Case splWraith 'Оружие: Уменьшает случайную характеристику цели. ret = "Hand of the Wraith" Case splThief 'Оружие: Забирает случайное количество от параметра цели и передает персонажу. ret = "Spell of The Phantom" Case Else ret = "No spell." End Select Return ret End Function
Эта функция просто смотрит на id заклинания и возвращает имя, связанное с
этим идентификатором. Ничего сложного.
inv.bi
'Возвращает эффект заклиания. Используется в описании. Function GetSpellEffect(spl As spellid) As String Dim As String ret = "" Select Case spl Case splMaxHealing 'Восстанавливает здоровье до максимума. ret = "Restore full health." Case splStrongMeat 'Добавляет временный бонус к силе. ret = "Adds bonus to strength." Case splBreadLife 'Лечит отравление. ret = "Cures poison." Case splMaxMana 'Полностью восстанавливает ману. ret = "Restore full mana." Case splSerpentBite 'Оружие: Наносит урон ядом. ret = "Inflicts poison damage over time." Case splRend 'Оружие: Уменьшает броню цели. ret = "Decreases armor of target." Case splSunder 'Оружие: Снижает наносимый целью урон оружием. ret = "Decreases target weapon damage." Case splReaper 'Оружие: Вызывает у монстра приступ страха. ret = "Causes monster to flee." Case splFire 'Оружие: Поджигает цель. ret = "Inflicts fire damage over time." Case splGoliath 'Оружие: Добавляет к атаке дополнительную силу. ret = "Adds additional strength to damage." Case splStun 'Оружие: Обездвиживает цель на время. ret = "Stuns target doing damage for a time." Case splChaos 'Оружие: Добавляет случайные повреждения. ret = "Adds random damage to attack." Case splWraith 'Оружие: Уменьшает случайную характеристику цели. ret = "Decreases random attribute of target." Case splThief 'Оружие: Забирает случайное количество от параметра цели и передает персонажу. ret = "Steals random attribute amount and adds to character." Case Else ret = "No effect." End Select Return ret End Function
GetSpellEffect возвращает описание заклинания. Оно нужно, чтобы игрок знал,
какой эффект будет от использования того или иного заклинания. Название и
описание заклинания отображается во время просмотра игроком описания предмета.
Отображая описание заклинания мы избавим игрока от необходимости запоминать, что
именно делает данное заклинание, что сделает игру более комфортной.
Давайте посмотрим, как мы показываем эту информацию
игроку.
inv.bi
'Возврайает подробное описание предмета. Sub GetFullDesc(lines() As String, inv As invtype) Dim As Integer idx = 0 'Очистим массив. Redim lines(0 To idx) As String 'Убедимся, что есть что инспектировать. If inv.classid <> clNone Then 'Выберем предмет. Select Case inv.classid ... Case clWeapon idx += 1 Redim Preserve lines(0 To idx) As String lines(idx) = GetInvItemDesc(inv) idx += 1 Redim Preserve lines(0 To idx) As String lines(idx) = "* " & inv.weapon.dam & " weapon damage" idx += 1 Redim Preserve lines(0 To idx) As String lines(idx) = "* Hands Required: " & inv.weapon.hands If inv.weapon.weapontype = wtProjectile Then idx += 1 Redim Preserve lines(0 To idx) As String lines(idx) = "* Capacity: " & inv.weapon.capacity idx += 1 Redim Preserve lines(0 To idx) As String lines(idx) = "* Ammo Cnt: " & inv.weapon.ammocnt Endif If IsEval(inv) = TRUE Then If inv.weapon.spell.id <> splNone Then idx += 1 Redim Preserve lines(0 To idx) As String lines(idx) = "* Spell: " & Trim(inv.weapon.spell.splname) idx += 1 Redim Preserve lines(0 To idx) As String lines(idx) = "* Effect: " & Trim(inv.weapon.spell.spldesc) End If idx += 1 Redim Preserve lines(0 To idx) As String lines(idx) = "* Item is evaluated" Else idx += 1 Redim Preserve lines(0 To idx) As String lines(idx) = "* Item is not evaluated" End If ... End Select Endif End Sub
Для ясности здесь показан только раздел с оружием. Мы получаем описание
предмета и наносимый оружием урон, как делали это раньше. Затем мы проверяем,
определен ли предмет, и если это так, то получаем название заклинания с его
описанием и показываем их игроку. Если предмет не опознан, то мы не отображаем
информацию о заклинании, а просто указываем игроку, что данный предмет еще не
инспектировался. Игрок не будет знать, есть ли какие либо магические свойства у
предмета, пока не определит его, что добавит дополнительный интерес
игре.
Теперь наши заклинания на своем месте и мы должны добавить их в
процедуры боя.
dod.bas
'Расчет ближнего боя. Sub DoMeleeCombat(mx As Integer, my As Integer) Dim As Integer cf, df, croll, mroll, dam, isdead, midx, mxp, xp Dim As String txt, mname Dim As Single marm Dim As spelltype spl1, spl2 'Поучим фактор защиты монстра. df = level.GetMonsterDefense(mx, my) mname = level.GetMonsterName(mx, my) 'Получим здоровье монстра. mxp = level.GetMonsterXP(mx, my) 'Убедимся что есть данные. If df > 0 Then 'Возвращает фактор атаки основанный на том, чем вооружен персонаж. cf = pchar.GetMeleeCombatFactor() 'Возьмем случайные значения. croll = RandomRange(1, cf) mroll = RandomRange(1, df) 'Если персонаж ударил монстра. If croll > mroll Then 'Получим наносимое оружием повреждение. dam = pchar.GetWeaponDamage() 'Получим броню монстра. marm = level.GetMonsterArmor(mx, my) 'Рассчитаем повреждения, основанное на рейтинге брони. dam = dam - (dam * marm) If dam <= 0 Then dam = 1 'Проверим, есть ли опознанное магическое оружие в обоих слотах, . spl1 = pchar.GetWeaponSpell(wPrimary) spl2 = pchar.GetWeaponSpell(wSecondary) 'Проверим заклинание «голиаф» (добавляет силу). If spl1.id = splGoliath Then dam += pchar.CurrStr If spl2.id = splGoliath Then dam += pchar.CurrStr 'Изменим здоровье монстра. isdead = level.ApplyDamage(mx, my, dam) 'Убедимся, что монстр жив. If isdead = FALSE Then 'Применим заклинание. If spl1.id <> splNone Then isdead = DoWeaponSpell(spl1, mx, my) Endif If isdead = FALSE Then If spl2.id <> splNone Then isdead = DoWeaponSpell(spl2, mx, my) End If Endif Endif 'Если монстр умер. If isdead = TRUE Then 'Добавим персонажу опыт. xp = pchar.CurrXP xp += mxp pchar.CurrXP = xp xp = pchar.TotXP xp += mxp pchar.TotXP = xp 'Напечатаем сообщение. txt = pchar.CharName & " killed the " & mname & " with " & dam & " damage points." PrintMessage txt Else txt = pchar.CharName & " hit the " & mname & " for " & dam & " damage points." PrintMessage txt Endif Else txt = pchar.CharName & " missed the " & mname & "." PrintMessage txt Endif Endif End Sub
Как вы можете видеть, нам не пришлось добавлять много кода для реализации
магической системы. Вот внесенные изменения:
dod.bas:DoMeleeCombat
'Проверим, есть ли опознанное магическое оружие в обоих слотах, . spl1 = pchar.GetWeaponSpell(wPrimary) spl2 = pchar.GetWeaponSpell(wSecondary) 'Проверим заклинание «голиаф» (добавляет силу). If spl1.id = splGoliath Then dam += pchar.CurrStr If spl2.id = splGoliath Then dam += pchar.CurrStr 'Изменим здоровье монстра. isdead = level.ApplyDamage(mx, my, dam) 'Убедимся, что монстр жив. If isdead = FALSE Then 'Применим заклинание. If spl1.id <> splNone Then isdead = DoWeaponSpell(spl1, mx, my) Endif If isdead = FALSE Then If spl2.id <> splNone Then isdead = DoWeaponSpell(spl2, mx, my) End If Endif Endif
GetWeaponSpell возвращает информацию о заклинании во временную структуру. Нам
необходима вся информация о заклинании, когда мы его применяем. Кроме того, нам
нужно проверить оба слота персонажа, так как у него в руках могут быть два
одноручных оружия. Теперь мы должны проверить — не является ли текущее
заклинание, заклинанием «голиаф», так как оно добавляет силу к удару, а значит и
урону наносимому текущим оружием, и его эффект должен быть применен здесь, а не
позже, как у большинства заклинаний. Если после нанесения повреждений оружием
монстр все еще жив, мы переменяем у нему эффекты заклинаний. Опять же мы должны
проверить оба слота персонажа, так как у него в руках могут быть два магических
оружия.
character.bi
'Возвращает информацию о заклинании Function character.GetWeaponSpell(wslot As wieldpos) As spelltype Dim ret As spelltype 'Очистим структуру. ClearSpell ret 'Проверим слот на наличие в нем оружия. If _cinfo.cwield(wslot).classid = clWeapon Then 'Проверим, опознано ли оно. If _cinfo.cwield(wslot).weapon.eval = TRUE Then 'Вернем заклинание; возможно splNone. ret = _cinfo.cwield(wslot).weapon.spell End If End If Return ret End Function
Функция GetWeaponSpell является частью объекта персонажа и просто возвращает
данные о заклинании оружия в определенном слоте персонажа, если оно было
определено. Подпрограмма ClearSpell устанавливает возвращаемое значение в
splNone, так что. Если оружие небыло определено, функция вернет splNone в
качестве идентификатора заклинания. Что произойдет если оружие не содержит
заклинания? Функция вернет splNone, так как мы вызывали функцию ClearSpell во
время создания оружия до назначения ему идентификатора заклинания. Это
гарантирует, что всегда вернется правильный идентификатор заклинания, какое бы
оружие не находилось в руках у персонажа.
dod.bas
'Применяет активное оружейное заклинание. Function DoWeaponSpell (spl As spelltype, mx As Integer, my As Integer) As Integer Dim As Integer ret = FALSE, statamt, rstat Dim As monStats mstat Dim As String splname If spl.id = splThief Then 'Получим случайную характеристику. mstat = RandomRange(CombatFactor, MagicDefense) statamt = level.GetMonsterStatAmt(mstat, mx, my) rstat = RandomRange(1, statamt - 1) 'Добавим временное бонусное значение характеристики персонажу. Select Case mstat Case CombatFactor pchar.BonAcf = rstat pchar.BonAcfCnt = spl.lvl Case CombatDefense pchar.BonCdf = rstat pchar.BonCdfCnt = spl.lvl Case MagicCombat pchar.BonMcf = rstat pchar.BonMcfCnt = spl.lvl Case MagicDefense pchar.BonMdf = rstat pchar.BonMdfCnt = spl.lvl End Select Else 'Применим заклинание к монстру. ret = level.ApplySpell(spl, mx, my) Endif Return ret End Function
DoWeaponSpell применяет эффект заклинания к монстру. Обратите внимание, что
мы проверяем, является ли текущее заклинание. Заклинанием «Вор», так как оно
добавляет бонус украденной у монстра характеристики персонажу. Различные боевые
характеристики монстра перечислены в файле monster.bi.
monster.bi
'Характеристики монстров, используемых заклинаниями. Enum monStats CombatFactor = 1 CombatDefense MagicCombat MagicDefense End Enum
Вначале случайным образом выбирается боевая характеристика монстра, а затем,
при помощи функции GetMonsterStatAmt получаем ее
значение.
monster.bi
'Возвращает текущее значение характеристики монстра Function levelobj.GetMonsterStatAmt(stat As monStats, mx As Integer, my As Integer) As Integer Dim As Integer midx, ret = 0 If _level.lmap(mx, my).monidx > 0 Then midx = _level.lmap(mx, my).monidx If stat = CombatFactor Then ret = _level.moninfo(midx).cf Elseif stat = CombatDefense Then ret = _level.moninfo(midx).cd Elseif stat = MagicCombat Then ret = _level.moninfo(midx).mf Elseif stat = MagicDefense Then ret = _level.moninfo(midx).md Endif End If Return ret End Function
Функция просто проверяет — значение какого параметра нам нужно и возвращает
его. Так как список монстров у нас находится в объекте уровня подземелья. То
данная функция также принадлежит объекту уровня.
Как только мы получили
значение параметра, нам необходимо применить его к
персонажу.
dod.bas:DoWeaponSpell
... Select Case mstat Case CombatFactor pchar.BonAcf = rstat pchar.BonAcfCnt = spl.lvl ...
Значение записывается в поле бонуса соответствующего параметра персонажа, а
его продолжительность зависит от уровня заклинания. Чем больше уровень
подземелья, тем выше уровень у заклинания и тем больше продолжительность
действия бонуса. Однако стоит иметь ввиду, что только один бонус может быть
активным одновременно, что заставит игрока решать, какое оружие и с каким
заклинанием ему стоит использовать в данный момент времени. Это добавляет игре
еще больше тактических аспектов, а также не позволит ему становиться слишком
сильным. Если бы бонусы имели накопительный эффект, то игра стала бы очень
простой и скучной.
Если передаваемое в функцию DoWeaponSpell заклинание
не является заклинанием «Вор», то мы должны применить его эффект на монстра, что
мы и делаем при помощи функции объекта уровня ApplySpell.
map.bi
'Применить заклинание на монстра. Function levelobj.ApplySpell(spl As spelltype, mx As Integer, my As Integer) As Integer Dim As Integer ret, midx Dim stat As monStats Dim As String txt 'Убедимся что здесь есть монстр. If _level.lmap(mx, my).monidx > 0 Then 'Получим индекс монстра. midx = _level.lmap(mx, my).monidx 'Установим монстру флаг в зависимости от заклинания. Select Case spl.id Case splSerpentBite 'Оружие: Наносит урон ядом. ret = ApplyDamage(mx, my, spl.dam) 'Если не умер, установим флаг времени. If ret = FALSE Then _level.moninfo(midx).effects(mePoison).cnt = spl.lvl _level.moninfo(midx).effects(mePoison).dam = spl.dam Endif txt = "Sperpent Bite Spell does " & spl.dam & " to " & _level.moninfo(midx).mname & "." PrintMessage txt Case splRend 'Оружие: Уменьшает броню цели. _level.moninfo(midx).armval = _level.moninfo(midx).armval - (spl.dam / maxlevel) If _level.moninfo(midx).armval < 0.0 Then _level.moninfo(midx).armval = 0.0 Endif txt = "Rend Spell reduces armor to " & _level.moninfo(midx).armval & " for " & _level.moninfo(midx).mname & "." PrintMessage txt Case splSunder 'Оружие: Снижает наносимый целью урон оружием. _level.moninfo(midx).atkdam = _level.moninfo(midx).atkdam - spl.dam If _level.moninfo(midx).atkdam < 1 Then _level.moninfo(midx).atkdam = 1 Endif txt = "Sunder Spell reduces attack damage to " & _level.moninfo(midx).atkdam & " for " & _level.moninfo(midx).mname & "." PrintMessage txt Case splReaper 'Оружие: Вызывает у монстра приступ страха. _level.moninfo(midx).flee = TRUE txt = "Reaper Spell is making " & _level.moninfo(midx).mname & " flee." PrintMessage txt Case splFire 'Оружие: Поджигает цель. ret = ApplyDamage(mx, my, spl.dam) 'If not dead set the timed flag. If ret = FALSE Then _level.moninfo(midx).effects(meFire).cnt = spl.lvl _level.moninfo(midx).effects(meFire).dam = spl.dam Endif txt = "Fire Spell inflicted " & spl.dam & " to " & _level.moninfo(midx).mname & "." PrintMessage txt Case splStun 'Оружие: Обездвиживает цель на время. _level.moninfo(midx).effects(meStun).cnt = spl.lvl _level.moninfo(midx).effects(meStun).dam = 0 txt = "Stun Spell stunned " & _level.moninfo(midx).mname & "." PrintMessage txt Case splChaos 'Оружие: Добавляет случайные повреждения. ret = ApplyDamage(mx, my, spl.dam) txt = "Chaos Spell inflicted " & spl.dam & " additional damage to " & _level.moninfo(midx).mname & "." PrintMessage txt Case splWraith 'Оружие: Уменьшает случайную характеристику цели. 'Get the stat. stat = RandomRange(CombatFactor, MagicDefense) Select Case stat Case CombatFactor _level.moninfo(midx).cf = _level.moninfo(midx).cf - spl.dam If _level.moninfo(midx).cf < 0 Then _level.moninfo(midx).cf = 1 Endif txt = "Wraith Spell reduced " & _level.moninfo(midx).mname & " combat factor by " & spl.dam & "." PrintMessage txt Case CombatDefense _level.moninfo(midx).cd = _level.moninfo(midx).cd - spl.dam If _level.moninfo(midx).cd < 0 Then _level.moninfo(midx).cd = 1 Endif txt = "Wraith Spell reduced " & _level.moninfo(midx).mname & " combat defense by " & spl.dam & "." PrintMessage txt Case MagicCombat _level.moninfo(midx).mf = _level.moninfo(midx).mf - spl.dam If _level.moninfo(midx).mf < 0 Then _level.moninfo(midx).mf = 1 Endif txt = "Wraith Spell reduced " & _level.moninfo(midx).mname & " magic combat by " & spl.dam & "." PrintMessage txt Case MagicDefense _level.moninfo(midx).md = _level.moninfo(midx).md - spl.dam If _level.moninfo(midx).md < 0 Then _level.moninfo(midx).md = 1 Endif txt = "Wraith Spell reduced " & _level.moninfo(midx).mname & " magic defense by " & spl.dam & "." PrintMessage txt End Select End Select End If Return ret End Function
Первое что нужно сделать, это убедиться. Что монстр, на которого нужно
применить заклинание, присутствует. Если это так, то мы применяем на наго эффект
заклинания основываясь на его идентификаторе. Каждое заклинание делает что то
свое и влияет на монстра по разному. Обратите внимание, что здесь мы имеем дело
именно с самим заклинанием, а не его источником. Это позволит добавлять в эту
функцию любые заклинания применимые к монстрам, а не только заклинания
привязанные к оружию. Именно для этого нам нужно было обобщить заклинания, в
результате чего мы будем использовать набор одних и тех же процедур для
управления всеми заклинаниями в игре.
Изучив код вы можете увидеть что
конкретно делает каждое заклинание. Обратите внимание, что поля lvl и dam в
каждом заклинании используются по разному. Поле dam является универсальным и
используется не только для нанесения повреждений, но, также, и для снижения
атрибутов монстра. Некоторые заклинания влияют на монстров с течением времени.
Например, заклинание «Жнец», заставляет монстра убегать, установив в TRUE
специальный атрибут. Заклинание «Оглушить» временно замораживает монстра,
заставляя его пропуска все атаки и передвижения. Продолжительность большинства
эффектов зависит от уровня заклинания, поэтому чем выше уровень заклинания, тем
дольше его эффект будет действовать на монстра.
Так как в нашей игре
присутствует не только ближний бой, но и дистанционный, то нам необходимо
добавить применение эффектов заклинаний и для дистанционных
атак.
dod.bas
'Расчет дистанционных атак Sub DoProjectileCombat(mx As Integer, my As Integer, wslot As wieldpos) Dim As Integer cf, df, croll, mroll, dam, isdead, midx, mxp, xp Dim As String txt, mname Dim As Single marm Dim As spelltype spl1, spl2 'Получим фактор защиты монстра. If pchar.ProjectileIsWand(wslot) = TRUE Then df = level.GetMonsterMagicDefense(mx, my) Else df = level.GetMonsterDefense(mx, my) Endif 'Получим имя монстра. mname = level.GetMonsterName(mx, my) 'Получим здоровье монстра. mxp = level.GetMonsterXP(mx, my) 'Убедимся что данные получены. If df > 0 Then 'Получим файтор атаки, основанный на том, чем вооружен персонаж. If pchar.ProjectileIsWand(wslot) = TRUE Then cf = pchar.GetMagicCombatFactor() Else cf = pchar.GetProjectileCombatFactor() Endif 'Возьмем случайные значения. croll = RandomRange(1, cf) mroll = RandomRange(1, df) 'Если персонаж попал по монстру. If croll > mroll Then 'Получим наночимы оружием повреждения. dam = pchar.GetWeaponDamage(wslot) 'Получим рейтинг брони монстра. marm = level.GetMonsterArmor(mx, my) 'Применим повреждения зависящие от брони. dam = dam - (dam * marm) If dam <= 0 Then dam = 1 'Проверим заклинания для оружия в обоих слотах. Если опознано. spl1 = pchar.GetWeaponSpell(wPrimary) spl2 = pchar.GetWeaponSpell(wSecondary) 'Если есть заклинание «голиаф». If spl1.id = splGoliath Then dam += pchar.CurrStr If spl2.id = splGoliath Then dam += pchar.CurrStr 'Изменим здоровье монстра. isdead = level.ApplyDamage(mx, my, dam) 'Если монстр все еще жив. If isdead = FALSE Then 'Применим эффект заклинания. If spl1.id <> splNone Then isdead = DoWeaponSpell(spl1, mx, my) Endif If isdead = FALSE Then If spl2.id <> splNone Then isdead = DoWeaponSpell(spl2, mx, my) End If Endif Endif 'Если монстр умер. If isdead = TRUE Then 'Добавим опыт персонажу. xp = pchar.CurrXP xp += mxp pchar.CurrXP = xp xp = pchar.TotXP xp += mxp pchar.TotXP = xp 'Вывод сообщения. txt = pchar.CharName & " killed the " & mname & " with " & dam & " damage points." PrintMessage txt Else txt = pchar.CharName & " hit the " & mname & " for " & dam & " damage points." PrintMessage txt Endif Else txt = pchar.CharName & " missed the " & mname & "." PrintMessage txt Endif Endif End Sub
Это обновленный код расчета дистанционных атак. Как вы можете видеть, он
почти в точности повторяет код атак ближнего боя. Мы получаем информацию о
заклинаниях, если таковые имеются, проверяем наличие заклинания «голиаф».
Наносим монстру повреждения от оружия, и, затем, применяем остальные эффекты.
Это все что нам необходимо сделать, для добавления заклинаний для дистанционного
оружия.
Нам достаточно много пришлось поработать, для реализации
магической системы, но большая часть кода была очень простой, так как мы
обобщили данные заклинаний. Теперь у нас есть базовая магическая система, к
которой мы, достаточно легко, можем добавить заклинания для доспехов и ювелирных
изделий. Так как мы будем использовать данную систему в качестве основы, то для
добавления новых заклинаний нам потребуется просто обновить некоторые процедуры
для применения и обработки новых эффектов.
Перевод на русский: Fantik
содержание | назад | вперед