diff --git a/Source/controls/modifier_hints.cpp b/Source/controls/modifier_hints.cpp index 97fa681a5..637392d8a 100644 --- a/Source/controls/modifier_hints.cpp +++ b/Source/controls/modifier_hints.cpp @@ -132,7 +132,7 @@ void DrawSpellsCircleMenuHint(const Surface &out, const Point &origin) splId = myPlayer._pSplHotKey[slot]; if (IsValidSpell(splId) && (spells & GetSpellBitmask(splId)) != 0) - splType = (leveltype == DTYPE_TOWN && !GetSpellData(splId).sTownSpell) ? SpellType::Invalid : myPlayer._pSplTHotKey[slot]; + splType = (leveltype == DTYPE_TOWN && !GetSpellData(splId).isAllowedInTown()) ? SpellType::Invalid : myPlayer._pSplTHotKey[slot]; else { splType = SpellType::Invalid; splId = SpellID::Null; diff --git a/Source/controls/plrctrls.cpp b/Source/controls/plrctrls.cpp index b00781e9e..e90707676 100644 --- a/Source/controls/plrctrls.cpp +++ b/Source/controls/plrctrls.cpp @@ -231,8 +231,8 @@ bool HasRangedSpell() return spl != SpellID::Invalid && spl != SpellID::TownPortal && spl != SpellID::Teleport - && GetSpellData(spl).sTargeted - && !GetSpellData(spl).sTownSpell; + && GetSpellData(spl).isTargeted() + && !GetSpellData(spl).isAllowedInTown(); } bool CanTargetMonster(const Monster &monster) @@ -2049,7 +2049,7 @@ void CtrlUseInvItem() if (TargetsMonster(item._iSpell)) { return; } - if (GetSpellData(item._iSpell).sTargeted) { + if (GetSpellData(item._iSpell).isTargeted()) { UpdateSpellTarget(item._iSpell); } } @@ -2076,7 +2076,7 @@ void CtrlUseStashItem() if (TargetsMonster(item._iSpell)) { return; } - if (GetSpellData(item._iSpell).sTargeted) { + if (GetSpellData(item._iSpell).isTargeted()) { UpdateSpellTarget(item._iSpell); } } diff --git a/Source/inv.cpp b/Source/inv.cpp index 2b3393eb2..18ae9ba16 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -1971,7 +1971,7 @@ void ConsumeScroll(Player &player) bool CanUseScroll(Player &player, SpellID spell) { - if (leveltype == DTYPE_TOWN && !GetSpellData(spell).sTownSpell) + if (leveltype == DTYPE_TOWN && !GetSpellData(spell).isAllowedInTown()) return false; return HasInventoryOrBeltItem(player, [spell](const Item &item) { @@ -2100,7 +2100,7 @@ bool UseInvItem(size_t pnum, int cii) dropGoldValue = 0; } - if (item->isScroll() && leveltype == DTYPE_TOWN && !GetSpellData(item->_iSpell).sTownSpell) { + if (item->isScroll() && leveltype == DTYPE_TOWN && !GetSpellData(item->_iSpell).isAllowedInTown()) { return true; } diff --git a/Source/itemdat.h b/Source/itemdat.h index b52c8cd88..fbee854bf 100644 --- a/Source/itemdat.h +++ b/Source/itemdat.h @@ -453,7 +453,7 @@ struct ItemData { uint8_t iMinDex; ItemSpecialEffect iFlags; // ItemSpecialEffect as bit flags enum item_misc_id iMiscId; - enum SpellID iSpell; + SpellID iSpell; bool iUsable; uint16_t iValue; }; diff --git a/Source/items.cpp b/Source/items.cpp index 11e2df886..9e5302284 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -613,7 +613,7 @@ void GetBookSpell(Item &item, int lvl) lvl = 5; int s = static_cast(SpellID::Firebolt); - enum SpellID bs = SpellID::Firebolt; + SpellID bs = SpellID::Firebolt; while (rv > 0) { int sLevel = GetSpellBookLevel(static_cast(s)); if (sLevel != -1 && lvl >= sLevel) { @@ -638,15 +638,21 @@ void GetBookSpell(Item &item, int lvl) CopyUtf8(item._iName + iNameLen, spellName, sizeof(item._iName) - iNameLen); CopyUtf8(item._iIName + iINameLen, spellName, sizeof(item._iIName) - iINameLen); item._iSpell = bs; - item._iMinMag = GetSpellData(bs).sMinInt; - item._ivalue += GetSpellData(bs).sBookCost; - item._iIvalue += GetSpellData(bs).sBookCost; - if (GetSpellData(bs).sType == MagicType::Fire) + const SpellData &spellData = GetSpellData(bs); + item._iMinMag = spellData.minInt; + item._ivalue += spellData.bookCost(); + item._iIvalue += spellData.bookCost(); + switch (spellData.type()) { + case MagicType::Fire: item._iCurs = ICURS_BOOK_RED; - else if (GetSpellData(bs).sType == MagicType::Lightning) + break; + case MagicType::Lightning: item._iCurs = ICURS_BOOK_BLUE; - else if (GetSpellData(bs).sType == MagicType::Magic) + break; + case MagicType::Magic: item._iCurs = ICURS_BOOK_GREY; + break; + } } int RndPL(int param1, int param2) @@ -1211,7 +1217,7 @@ void GetStaffSpell(const Player &player, Item &item, int lvl, bool onlygood) lvl = 10; int s = static_cast(SpellID::Firebolt); - enum SpellID bs = SpellID::Null; + SpellID bs = SpellID::Null; while (rv > 0) { int sLevel = GetSpellStaffLevel(static_cast(s)); if (sLevel != -1 && l >= sLevel) { @@ -1233,8 +1239,8 @@ void GetStaffSpell(const Player &player, Item &item, int lvl, bool onlygood) item._iCharges = minc + GenerateRnd(maxc); item._iMaxCharges = item._iCharges; - item._iMinMag = GetSpellData(bs).sMinInt; - int v = item._iCharges * GetSpellData(bs).sStaffCost / 5; + item._iMinMag = GetSpellData(bs).minInt; + int v = item._iCharges * GetSpellData(bs).staffCost() / 5; item._ivalue += v; item._iIvalue += v; GetStaffPower(player, item, lvl, bs, onlygood); @@ -3825,7 +3831,7 @@ void UseItem(size_t pnum, item_misc_id mid, SpellID spl) break; case IMISC_SCROLL: case IMISC_SCROLLT: - if (ControlMode == ControlTypes::KeyboardAndMouse && GetSpellData(spl).sTargeted) { + if (ControlMode == ControlTypes::KeyboardAndMouse && GetSpellData(spl).isTargeted()) { player._pTSpell = spl; if (&player == MyPlayer) NewCursor(CURSOR_TELEPORT); @@ -4498,7 +4504,7 @@ void Item::setNewAnimation(bool showAnimation) void Item::updateRequiredStatsCacheForPlayer(const Player &player) { if (_itype == ItemType::Misc && _iMiscId == IMISC_BOOK) { - _iMinMag = GetSpellData(_iSpell).sMinInt; + _iMinMag = GetSpellData(_iSpell).minInt; int8_t spellLevel = player._pSplLvl[static_cast(_iSpell)]; while (spellLevel != 0) { _iMinMag += 20 * _iMinMag / 100; diff --git a/Source/msg.cpp b/Source/msg.cpp index 05c291db9..3589b0327 100644 --- a/Source/msg.cpp +++ b/Source/msg.cpp @@ -1472,7 +1472,7 @@ size_t OnSpellWall(const TCmd *pCmd, Player &player) LogError(_("{:s} has cast an invalid spell."), player._pName); return sizeof(message); } - if (leveltype == DTYPE_TOWN && !GetSpellData(spell).sTownSpell) { + if (leveltype == DTYPE_TOWN && !GetSpellData(spell).isAllowedInTown()) { LogError(_("{:s} has cast an illegal spell."), player._pName); return sizeof(message); } @@ -1513,7 +1513,7 @@ size_t OnSpellTile(const TCmd *pCmd, Player &player) LogError(_("{:s} has cast an invalid spell."), player._pName); return sizeof(message); } - if (leveltype == DTYPE_TOWN && !GetSpellData(spell).sTownSpell) { + if (leveltype == DTYPE_TOWN && !GetSpellData(spell).isAllowedInTown()) { LogError(_("{:s} has cast an illegal spell."), player._pName); return sizeof(message); } @@ -1548,7 +1548,7 @@ size_t OnTargetSpellTile(const TCmd *pCmd, Player &player) return sizeof(message); auto spell = static_cast(wParam1); - if (leveltype == DTYPE_TOWN && !GetSpellData(spell).sTownSpell) { + if (leveltype == DTYPE_TOWN && !GetSpellData(spell).isAllowedInTown()) { LogError(_("{:s} has cast an illegal spell."), player._pName); return sizeof(message); } @@ -1663,7 +1663,7 @@ size_t OnSpellMonster(const TCmd *pCmd, Player &player) LogError(_("{:s} has cast an invalid spell."), player._pName); return sizeof(message); } - if (leveltype == DTYPE_TOWN && !GetSpellData(spell).sTownSpell) { + if (leveltype == DTYPE_TOWN && !GetSpellData(spell).isAllowedInTown()) { LogError(_("{:s} has cast an illegal spell."), player._pName); return sizeof(message); } @@ -1702,7 +1702,7 @@ size_t OnSpellPlayer(const TCmd *pCmd, Player &player) LogError(_("{:s} has cast an invalid spell."), player._pName); return sizeof(message); } - if (leveltype == DTYPE_TOWN && !GetSpellData(spell).sTownSpell) { + if (leveltype == DTYPE_TOWN && !GetSpellData(spell).isAllowedInTown()) { LogError(_("{:s} has cast an illegal spell."), player._pName); return sizeof(message); } @@ -1737,7 +1737,7 @@ size_t OnTargetSpellMonster(const TCmd *pCmd, Player &player) return sizeof(message); auto spell = static_cast(wParam2); - if (leveltype == DTYPE_TOWN && !GetSpellData(spell).sTownSpell) { + if (leveltype == DTYPE_TOWN && !GetSpellData(spell).isAllowedInTown()) { LogError(_("{:s} has cast an illegal spell."), player._pName); return sizeof(message); } @@ -1772,7 +1772,7 @@ size_t OnTargetSpellPlayer(const TCmd *pCmd, Player &player) return sizeof(message); auto spell = static_cast(wParam2); - if (leveltype == DTYPE_TOWN && !GetSpellData(spell).sTownSpell) { + if (leveltype == DTYPE_TOWN && !GetSpellData(spell).isAllowedInTown()) { LogError(_("{:s} has cast an illegal spell."), player._pName); return sizeof(message); } diff --git a/Source/panels/spell_book.cpp b/Source/panels/spell_book.cpp index 383a3ca83..5e9c3cffb 100644 --- a/Source/panels/spell_book.cpp +++ b/Source/panels/spell_book.cpp @@ -68,7 +68,7 @@ SpellType GetSBookTrans(SpellID ii, bool townok) st = SpellType::Invalid; } } - if (townok && leveltype == DTYPE_TOWN && st != SpellType::Invalid && !GetSpellData(ii).sTownSpell) { + if (townok && leveltype == DTYPE_TOWN && st != SpellType::Invalid && !GetSpellData(ii).isAllowedInTown()) { st = SpellType::Invalid; } diff --git a/Source/panels/spell_list.cpp b/Source/panels/spell_list.cpp index a3d1cd44b..d9b0f5d51 100644 --- a/Source/panels/spell_list.cpp +++ b/Source/panels/spell_list.cpp @@ -94,7 +94,7 @@ void DrawSpell(const Surface &out) st = SpellType::Invalid; } - if (leveltype == DTYPE_TOWN && st != SpellType::Invalid && !GetSpellData(spl).sTownSpell) + if (leveltype == DTYPE_TOWN && st != SpellType::Invalid && !GetSpellData(spl).isAllowedInTown()) st = SpellType::Invalid; SetSpellTrans(st); @@ -117,7 +117,7 @@ void DrawSpellList(const Surface &out) SpellType transType = spellListItem.type; int spellLevel = 0; const SpellData &spellDataItem = GetSpellData(spellListItem.id); - if (leveltype == DTYPE_TOWN && !spellDataItem.sTownSpell) { + if (leveltype == DTYPE_TOWN && !spellDataItem.isAllowedInTown()) { transType = SpellType::Invalid; } if (spellListItem.type == SpellType::Spell) { diff --git a/Source/player.cpp b/Source/player.cpp index 764be982c..8ac4f2994 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -432,7 +432,7 @@ void StartRangeAttack(Player &player, Direction d, WorldTileCoord cx, WorldTileC player_graphic GetPlayerGraphicForSpell(SpellID spellId) { - switch (GetSpellData(spellId).sType) { + switch (GetSpellData(spellId).type()) { case MagicType::Fire: return player_graphic::Fire; case MagicType::Lightning: @@ -2018,15 +2018,7 @@ player_graphic Player::getGraphic() const case PM_BLOCK: return player_graphic::Block; case PM_SPELL: - switch (GetSpellData(executedSpell.spellId).sType) { - case MagicType::Fire: - return player_graphic::Fire; - case MagicType::Lightning: - return player_graphic::Lightning; - case MagicType::Magic: - return player_graphic::Magic; - } - return player_graphic::Fire; + return GetPlayerGraphicForSpell(executedSpell.spellId); case PM_GOTHIT: return player_graphic::Hit; case PM_DEATH: @@ -3453,7 +3445,7 @@ void CheckPlrSpell(bool isShiftHeld, SpellID spellID, SpellType spellType) } } - if (leveltype == DTYPE_TOWN && !GetSpellData(spellID).sTownSpell) { + if (leveltype == DTYPE_TOWN && !GetSpellData(spellID).isAllowedInTown()) { myPlayer.Say(HeroSpeech::ICantCastThatHere); return; } diff --git a/Source/spelldat.cpp b/Source/spelldat.cpp index 6cf0cfb66..83aa798ab 100644 --- a/Source/spelldat.cpp +++ b/Source/spelldat.cpp @@ -8,62 +8,70 @@ namespace devilution { +namespace { +const auto Fire = SpellDataFlags::Fire; +const auto Lightning = SpellDataFlags::Lightning; +const auto Magic = SpellDataFlags::Magic; +const auto Targeted = SpellDataFlags::Targeted; +const auto AllowedInTown = SpellDataFlags::AllowedInTown; +} // namespace + /** Data related to each spell ID. */ const SpellData SpellsData[] = { // clang-format off -// sName, sManaCost, sType, sNameText, sBookLvl, sStaffLvl, sTargeted, sTownSpell, sMinInt, sSFX, { sMissiles[3], } sManaAdj, sMinMana, sStaffMin, sStaffMax, sBookCost, sStaffCost -{ SpellID::Null, 0, MagicType::Fire, nullptr, 0, 0, false, false, 0, SFX_NONE, { MissileID::Null, MissileID::Null, MissileID::Null }, 0, 0, 40, 80, 0, 0 }, -{ SpellID::Firebolt, 6, MagicType::Fire, P_("spell", "Firebolt"), 1, 1, true, false, 15, IS_CAST2, { MissileID::Firebolt, MissileID::Null, MissileID::Null }, 1, 3, 40, 80, 1000, 50 }, -{ SpellID::Healing, 5, MagicType::Magic, P_("spell", "Healing"), 1, 1, false, true, 17, IS_CAST8, { MissileID::Healing, MissileID::Null, MissileID::Null }, 3, 1, 20, 40, 1000, 50 }, -{ SpellID::Lightning, 10, MagicType::Lightning, P_("spell", "Lightning"), 4, 3, true, false, 20, IS_CAST4, { MissileID::LightningControl, MissileID::Null, MissileID::Null }, 1, 6, 20, 60, 3000, 150 }, -{ SpellID::Flash, 30, MagicType::Lightning, P_("spell", "Flash"), 5, 4, false, false, 33, IS_CAST4, { MissileID::FlashBottom, MissileID::FlashTop, MissileID::Null }, 2, 16, 20, 40, 7500, 500 }, -{ SpellID::Identify, 13, MagicType::Magic, P_("spell", "Identify"), -1, -1, false, true, 23, IS_CAST6, { MissileID::Identify, MissileID::Null, MissileID::Null }, 2, 1, 8, 12, 0, 100 }, -{ SpellID::FireWall, 28, MagicType::Fire, P_("spell", "Fire Wall"), 3, 2, true, false, 27, IS_CAST2, { MissileID::FireWallControl, MissileID::Null, MissileID::Null }, 2, 16, 8, 16, 6000, 400 }, -{ SpellID::TownPortal, 35, MagicType::Magic, P_("spell", "Town Portal"), 3, 3, true, false, 20, IS_CAST6, { MissileID::TownPortal, MissileID::Null, MissileID::Null }, 3, 18, 8, 12, 3000, 200 }, -{ SpellID::StoneCurse, 60, MagicType::Magic, P_("spell", "Stone Curse"), 6, 5, true, false, 51, IS_CAST2, { MissileID::StoneCurse, MissileID::Null, MissileID::Null }, 3, 40, 8, 16, 12000, 800 }, -{ SpellID::Infravision, 40, MagicType::Magic, P_("spell", "Infravision"), -1, -1, false, false, 36, IS_CAST8, { MissileID::Infravision, MissileID::Null, MissileID::Null }, 5, 20, 0, 0, 0, 600 }, -{ SpellID::Phasing, 12, MagicType::Magic, P_("spell", "Phasing"), 7, 6, false, false, 39, IS_CAST2, { MissileID::Phasing, MissileID::Null, MissileID::Null }, 2, 4, 40, 80, 3500, 200 }, -{ SpellID::ManaShield, 33, MagicType::Magic, P_("spell", "Mana Shield"), 6, 5, false, false, 25, IS_CAST2, { MissileID::ManaShield, MissileID::Null, MissileID::Null }, 0, 33, 4, 10, 16000, 1200 }, -{ SpellID::Fireball, 16, MagicType::Fire, P_("spell", "Fireball"), 8, 7, true, false, 48, IS_CAST2, { MissileID::Fireball, MissileID::Null, MissileID::Null }, 1, 10, 40, 80, 8000, 300 }, -{ SpellID::Guardian, 50, MagicType::Fire, P_("spell", "Guardian"), 9, 8, true, false, 61, IS_CAST2, { MissileID::Guardian, MissileID::Null, MissileID::Null }, 2, 30, 16, 32, 14000, 950 }, -{ SpellID::ChainLightning, 30, MagicType::Lightning, P_("spell", "Chain Lightning"), 8, 7, false, false, 54, IS_CAST2, { MissileID::ChainLightning, MissileID::Null, MissileID::Null }, 1, 18, 20, 60, 11000, 750 }, -{ SpellID::FlameWave, 35, MagicType::Fire, P_("spell", "Flame Wave"), 9, 8, true, false, 54, IS_CAST2, { MissileID::FlameWaveControl, MissileID::Null, MissileID::Null }, 3, 20, 20, 40, 10000, 650 }, -{ SpellID::DoomSerpents, 0, MagicType::Lightning, P_("spell", "Doom Serpents"), -1, -1, false, false, 0, IS_CAST2, { MissileID::Null, MissileID::Null, MissileID::Null }, 0, 0, 40, 80, 0, 0 }, -{ SpellID::BloodRitual, 0, MagicType::Magic, P_("spell", "Blood Ritual"), -1, -1, false, false, 0, IS_CAST2, { MissileID::Null, MissileID::Null, MissileID::Null }, 0, 0, 40, 80, 0, 0 }, -{ SpellID::Nova, 60, MagicType::Magic, P_("spell", "Nova"), 14, 10, false, false, 87, IS_CAST4, { MissileID::Nova, MissileID::Null, MissileID::Null }, 3, 35, 16, 32, 21000, 1300 }, -{ SpellID::Invisibility, 0, MagicType::Magic, P_("spell", "Invisibility"), -1, -1, false, false, 0, IS_CAST2, { MissileID::Null, MissileID::Null, MissileID::Null }, 0, 0, 40, 80, 0, 0 }, -{ SpellID::Inferno, 11, MagicType::Fire, P_("spell", "Inferno"), 3, 2, true, false, 20, IS_CAST2, { MissileID::InfernoControl, MissileID::Null, MissileID::Null }, 1, 6, 20, 40, 2000, 100 }, -{ SpellID::Golem, 100, MagicType::Fire, P_("spell", "Golem"), 11, 9, false, false, 81, IS_CAST2, { MissileID::Golem, MissileID::Null, MissileID::Null }, 6, 60, 16, 32, 18000, 1100 }, -{ SpellID::Rage, 15, MagicType::Magic, P_("spell", "Rage"), -1, -1, false, false, 0, IS_CAST8, { MissileID::Rage, MissileID::Null, MissileID::Null }, 1, 1, 0, 0, 0, 0 }, -{ SpellID::Teleport, 35, MagicType::Magic, P_("spell", "Teleport"), 14, 12, true, false, 105, IS_CAST6, { MissileID::Teleport, MissileID::Null, MissileID::Null }, 3, 15, 16, 32, 20000, 1250 }, -{ SpellID::Apocalypse, 150, MagicType::Fire, P_("spell", "Apocalypse"), 19, 15, false, false, 149, IS_CAST2, { MissileID::Apocalypse, MissileID::Null, MissileID::Null }, 6, 90, 8, 12, 30000, 2000 }, -{ SpellID::Etherealize, 100, MagicType::Magic, P_("spell", "Etherealize"), -1, -1, false, false, 93, IS_CAST2, { MissileID::Etherealize, MissileID::Null, MissileID::Null }, 0, 100, 2, 6, 26000, 1600 }, -{ SpellID::ItemRepair, 0, MagicType::Magic, P_("spell", "Item Repair"), -1, -1, false, true, -1, IS_CAST6, { MissileID::ItemRepair, MissileID::Null, MissileID::Null }, 0, 0, 40, 80, 0, 0 }, -{ SpellID::StaffRecharge, 0, MagicType::Magic, P_("spell", "Staff Recharge"), -1, -1, false, true, -1, IS_CAST6, { MissileID::StaffRecharge, MissileID::Null, MissileID::Null }, 0, 0, 40, 80, 0, 0 }, -{ SpellID::TrapDisarm, 0, MagicType::Magic, P_("spell", "Trap Disarm"), -1, -1, false, false, -1, IS_CAST6, { MissileID::TrapDisarm, MissileID::Null, MissileID::Null }, 0, 0, 40, 80, 0, 0 }, -{ SpellID::Elemental, 35, MagicType::Fire, P_("spell", "Elemental"), 8, 6, false, false, 68, IS_CAST2, { MissileID::Elemental, MissileID::Null, MissileID::Null }, 2, 20, 20, 60, 10500, 700 }, -{ SpellID::ChargedBolt, 6, MagicType::Lightning, P_("spell", "Charged Bolt"), 1, 1, true, false, 25, IS_CAST2, { MissileID::ChargedBolt, MissileID::Null, MissileID::Null }, 1, 6, 40, 80, 1000, 50 }, -{ SpellID::HolyBolt, 7, MagicType::Magic, P_("spell", "Holy Bolt"), 1, 1, true, false, 20, IS_CAST2, { MissileID::HolyBolt, MissileID::Null, MissileID::Null }, 1, 3, 40, 80, 1000, 50 }, -{ SpellID::Resurrect, 20, MagicType::Magic, P_("spell", "Resurrect"), -1, 5, false, true, 30, IS_CAST8, { MissileID::Resurrect, MissileID::Null, MissileID::Null }, 0, 20, 4, 10, 4000, 250 }, -{ SpellID::Telekinesis, 15, MagicType::Magic, P_("spell", "Telekinesis"), 2, 2, false, false, 33, IS_CAST2, { MissileID::Telekinesis, MissileID::Null, MissileID::Null }, 2, 8, 20, 40, 2500, 200 }, -{ SpellID::HealOther, 5, MagicType::Magic, P_("spell", "Heal Other"), 1, 1, false, true, 17, IS_CAST8, { MissileID::HealOther, MissileID::Null, MissileID::Null }, 3, 1, 20, 40, 1000, 50 }, -{ SpellID::BloodStar, 25, MagicType::Magic, P_("spell", "Blood Star"), 14, 13, false, false, 70, IS_CAST2, { MissileID::BloodStar, MissileID::Null, MissileID::Null }, 2, 14, 20, 60, 27500, 1800 }, -{ SpellID::BoneSpirit, 24, MagicType::Magic, P_("spell", "Bone Spirit"), 9, 7, false, false, 34, IS_CAST2, { MissileID::BoneSpirit, MissileID::Null, MissileID::Null }, 1, 12, 20, 60, 11500, 800 }, -{ SpellID::Mana, 255, MagicType::Magic, P_("spell", "Mana"), -1, 5, false, true, 17, IS_CAST8, { MissileID::Mana, MissileID::Null, MissileID::Null }, 3, 1, 12, 24, 1000, 50 }, -{ SpellID::Magi, 255, MagicType::Magic, P_("spell", "the Magi"), -1, 20, false, true, 45, IS_CAST8, { MissileID::Magi, MissileID::Null, MissileID::Null }, 3, 1, 15, 30, 100000, 200 }, -{ SpellID::Jester, 255, MagicType::Magic, P_("spell", "the Jester"), -1, 4, true, false, 30, IS_CAST8, { MissileID::Jester, MissileID::Null, MissileID::Null }, 3, 1, 15, 30, 100000, 200 }, -{ SpellID::LightningWall, 28, MagicType::Lightning, P_("spell", "Lightning Wall"), 3, 2, true, false, 27, IS_CAST4, { MissileID::LightningWallControl, MissileID::Null, MissileID::Null }, 2, 16, 8, 16, 6000, 400 }, -{ SpellID::Immolation, 60, MagicType::Fire, P_("spell", "Immolation"), 14, 10, false, false, 87, IS_CAST2, { MissileID::Immolation, MissileID::Null, MissileID::Null }, 3, 35, 16, 32, 21000, 1300 }, -{ SpellID::Warp, 35, MagicType::Magic, P_("spell", "Warp"), 3, 3, false, false, 25, IS_CAST6, { MissileID::Warp, MissileID::Null, MissileID::Null }, 3, 18, 8, 12, 3000, 200 }, -{ SpellID::Reflect, 35, MagicType::Magic, P_("spell", "Reflect"), 3, 3, false, false, 25, IS_CAST6, { MissileID::Reflect, MissileID::Null, MissileID::Null }, 3, 15, 8, 12, 3000, 200 }, -{ SpellID::Berserk, 35, MagicType::Magic, P_("spell", "Berserk"), 3, 3, true, false, 35, IS_CAST6, { MissileID::Berserk, MissileID::Null, MissileID::Null }, 3, 15, 8, 12, 3000, 200 }, -{ SpellID::RingOfFire, 28, MagicType::Fire, P_("spell", "Ring of Fire"), 5, 5, false, false, 27, IS_CAST2, { MissileID::RingOfFire, MissileID::Null, MissileID::Null }, 2, 16, 8, 16, 6000, 400 }, -{ SpellID::Search, 15, MagicType::Magic, P_("spell", "Search"), 1, 3, false, false, 25, IS_CAST6, { MissileID::Search, MissileID::Null, MissileID::Null }, 1, 1, 8, 12, 3000, 200 }, -{ SpellID::RuneOfFire, 255, MagicType::Magic, P_("spell", "Rune of Fire"), -1, -1, true, false, 48, IS_CAST8, { MissileID::RuneOfFire, MissileID::Null, MissileID::Null }, 1, 10, 40, 80, 8000, 300 }, -{ SpellID::RuneOfLight, 255, MagicType::Magic, P_("spell", "Rune of Light"), -1, -1, true, false, 48, IS_CAST8, { MissileID::RuneOfLight, MissileID::Null, MissileID::Null }, 1, 10, 40, 80, 8000, 300 }, -{ SpellID::RuneOfNova, 255, MagicType::Magic, P_("spell", "Rune of Nova"), -1, -1, true, false, 48, IS_CAST8, { MissileID::RuneOfNova, MissileID::Null, MissileID::Null }, 1, 10, 40, 80, 8000, 300 }, -{ SpellID::RuneOfImmolation, 255, MagicType::Magic, P_("spell", "Rune of Immolation"), -1, -1, true, false, 48, IS_CAST8, { MissileID::RuneOfImmolation, MissileID::Null, MissileID::Null }, 1, 10, 40, 80, 8000, 300 }, -{ SpellID::RuneOfStone, 255, MagicType::Magic, P_("spell", "Rune of Stone"), -1, -1, true, false, 48, IS_CAST8, { MissileID::RuneOfStone, MissileID::Null, MissileID::Null }, 1, 10, 40, 80, 8000, 300 }, +// id sNameText, sSFX, bookCost10, staffCost10, sManaCost, flags, sBookLvl, sStaffLvl, minInt, { sMissiles[2] } sManaAdj, sMinMana, sStaffMin, sStaffMax +/*SpellID::Null*/ { nullptr, SFX_NONE, 0, 0, 0, Fire, 0, 0, 0, { MissileID::Null, MissileID::Null, }, 0, 0, 40, 80 }, +/*SpellID::Firebolt*/ { P_("spell", "Firebolt"), IS_CAST2, 100, 5, 6, Fire | Targeted, 1, 1, 15, { MissileID::Firebolt, MissileID::Null, }, 1, 3, 40, 80 }, +/*SpellID::Healing*/ { P_("spell", "Healing"), IS_CAST8, 100, 5, 5, Magic | AllowedInTown, 1, 1, 17, { MissileID::Healing, MissileID::Null, }, 3, 1, 20, 40 }, +/*SpellID::Lightning*/ { P_("spell", "Lightning"), IS_CAST4, 300, 15, 10, Lightning | Targeted, 4, 3, 20, { MissileID::LightningControl, MissileID::Null, }, 1, 6, 20, 60 }, +/*SpellID::Flash*/ { P_("spell", "Flash"), IS_CAST4, 750, 50, 30, Lightning, 5, 4, 33, { MissileID::FlashBottom, MissileID::FlashTop }, 2, 16, 20, 40 }, +/*SpellID::Identify*/ { P_("spell", "Identify"), IS_CAST6, 0, 10, 13, Magic | AllowedInTown, -1, -1, 23, { MissileID::Identify, MissileID::Null, }, 2, 1, 8, 12 }, +/*SpellID::FireWall*/ { P_("spell", "Fire Wall"), IS_CAST2, 600, 40, 28, Fire | Targeted, 3, 2, 27, { MissileID::FireWallControl, MissileID::Null, }, 2, 16, 8, 16 }, +/*SpellID::TownPortal*/ { P_("spell", "Town Portal"), IS_CAST6, 300, 20, 35, Magic | Targeted, 3, 3, 20, { MissileID::TownPortal, MissileID::Null, }, 3, 18, 8, 12 }, +/*SpellID::StoneCurse*/ { P_("spell", "Stone Curse"), IS_CAST2, 1200, 80, 60, Magic | Targeted, 6, 5, 51, { MissileID::StoneCurse, MissileID::Null, }, 3, 40, 8, 16 }, +/*SpellID::Infravision*/ { P_("spell", "Infravision"), IS_CAST8, 0, 60, 40, Magic, -1, -1, 36, { MissileID::Infravision, MissileID::Null, }, 5, 20, 0, 0 }, +/*SpellID::Phasing*/ { P_("spell", "Phasing"), IS_CAST2, 350, 20, 12, Magic, 7, 6, 39, { MissileID::Phasing, MissileID::Null, }, 2, 4, 40, 80 }, +/*SpellID::ManaShield*/ { P_("spell", "Mana Shield"), IS_CAST2, 1600, 120, 33, Magic, 6, 5, 25, { MissileID::ManaShield, MissileID::Null, }, 0, 33, 4, 10 }, +/*SpellID::Fireball*/ { P_("spell", "Fireball"), IS_CAST2, 800, 30, 16, Fire | Targeted, 8, 7, 48, { MissileID::Fireball, MissileID::Null, }, 1, 10, 40, 80 }, +/*SpellID::Guardian*/ { P_("spell", "Guardian"), IS_CAST2, 1400, 95, 50, Fire | Targeted, 9, 8, 61, { MissileID::Guardian, MissileID::Null, }, 2, 30, 16, 32 }, +/*SpellID::ChainLightning*/ { P_("spell", "Chain Lightning"), IS_CAST2, 1100, 75, 30, Lightning, 8, 7, 54, { MissileID::ChainLightning, MissileID::Null, }, 1, 18, 20, 60 }, +/*SpellID::FlameWave*/ { P_("spell", "Flame Wave"), IS_CAST2, 1000, 65, 35, Fire | Targeted, 9, 8, 54, { MissileID::FlameWaveControl, MissileID::Null, }, 3, 20, 20, 40 }, +/*SpellID::DoomSerpents*/ { P_("spell", "Doom Serpents"), IS_CAST2, 0, 0, 0, Lightning, -1, -1, 0, { MissileID::Null, MissileID::Null, }, 0, 0, 40, 80 }, +/*SpellID::BloodRitual*/ { P_("spell", "Blood Ritual"), IS_CAST2, 0, 0, 0, Magic, -1, -1, 0, { MissileID::Null, MissileID::Null, }, 0, 0, 40, 80 }, +/*SpellID::Nova*/ { P_("spell", "Nova"), IS_CAST4, 2100, 130, 60, Magic, 14, 10, 87, { MissileID::Nova, MissileID::Null, }, 3, 35, 16, 32 }, +/*SpellID::Invisibility*/ { P_("spell", "Invisibility"), IS_CAST2, 0, 0, 0, Magic, -1, -1, 0, { MissileID::Null, MissileID::Null, }, 0, 0, 40, 80 }, +/*SpellID::Inferno*/ { P_("spell", "Inferno"), IS_CAST2, 200, 10, 11, Fire | Targeted, 3, 2, 20, { MissileID::InfernoControl, MissileID::Null, }, 1, 6, 20, 40 }, +/*SpellID::Golem*/ { P_("spell", "Golem"), IS_CAST2, 1800, 110, 100, Fire, 11, 9, 81, { MissileID::Golem, MissileID::Null, }, 6, 60, 16, 32 }, +/*SpellID::Rage*/ { P_("spell", "Rage"), IS_CAST8, 0, 0, 15, Magic, -1, -1, 0, { MissileID::Rage, MissileID::Null, }, 1, 1, 0, 0 }, +/*SpellID::Teleport*/ { P_("spell", "Teleport"), IS_CAST6, 2000, 125, 35, Magic | Targeted, 14, 12, 105, { MissileID::Teleport, MissileID::Null, }, 3, 15, 16, 32 }, +/*SpellID::Apocalypse*/ { P_("spell", "Apocalypse"), IS_CAST2, 3000, 200, 150, Fire, 19, 15, 149, { MissileID::Apocalypse, MissileID::Null, }, 6, 90, 8, 12 }, +/*SpellID::Etherealize*/ { P_("spell", "Etherealize"), IS_CAST2, 2600, 160, 100, Magic, -1, -1, 93, { MissileID::Etherealize, MissileID::Null, }, 0, 100, 2, 6 }, +/*SpellID::ItemRepair*/ { P_("spell", "Item Repair"), IS_CAST6, 0, 0, 0, Magic | AllowedInTown, -1, -1, 255, { MissileID::ItemRepair, MissileID::Null, }, 0, 0, 40, 80 }, +/*SpellID::StaffRecharge*/ { P_("spell", "Staff Recharge"), IS_CAST6, 0, 0, 0, Magic | AllowedInTown, -1, -1, 255, { MissileID::StaffRecharge, MissileID::Null, }, 0, 0, 40, 80 }, +/*SpellID::TrapDisarm*/ { P_("spell", "Trap Disarm"), IS_CAST6, 0, 0, 0, Magic, -1, -1, 255, { MissileID::TrapDisarm, MissileID::Null, }, 0, 0, 40, 80 }, +/*SpellID::Elemental*/ { P_("spell", "Elemental"), IS_CAST2, 1050, 70, 35, Fire, 8, 6, 68, { MissileID::Elemental, MissileID::Null, }, 2, 20, 20, 60 }, +/*SpellID::ChargedBolt*/ { P_("spell", "Charged Bolt"), IS_CAST2, 100, 5, 6, Lightning | Targeted, 1, 1, 25, { MissileID::ChargedBolt, MissileID::Null, }, 1, 6, 40, 80 }, +/*SpellID::HolyBolt*/ { P_("spell", "Holy Bolt"), IS_CAST2, 100, 5, 7, Magic | Targeted, 1, 1, 20, { MissileID::HolyBolt, MissileID::Null, }, 1, 3, 40, 80 }, +/*SpellID::Resurrect*/ { P_("spell", "Resurrect"), IS_CAST8, 400, 25, 20, Magic | AllowedInTown, -1, 5, 30, { MissileID::Resurrect, MissileID::Null, }, 0, 20, 4, 10 }, +/*SpellID::Telekinesis*/ { P_("spell", "Telekinesis"), IS_CAST2, 250, 20, 15, Magic, 2, 2, 33, { MissileID::Telekinesis, MissileID::Null, }, 2, 8, 20, 40 }, +/*SpellID::HealOther*/ { P_("spell", "Heal Other"), IS_CAST8, 100, 5, 5, Magic | AllowedInTown, 1, 1, 17, { MissileID::HealOther, MissileID::Null, }, 3, 1, 20, 40 }, +/*SpellID::BloodStar*/ { P_("spell", "Blood Star"), IS_CAST2, 2750, 180, 25, Magic, 14, 13, 70, { MissileID::BloodStar, MissileID::Null, }, 2, 14, 20, 60 }, +/*SpellID::BoneSpirit*/ { P_("spell", "Bone Spirit"), IS_CAST2, 1150, 80, 24, Magic, 9, 7, 34, { MissileID::BoneSpirit, MissileID::Null, }, 1, 12, 20, 60 }, +/*SpellID::Mana*/ { P_("spell", "Mana"), IS_CAST8, 100, 5, 255, Magic | AllowedInTown, -1, 5, 17, { MissileID::Mana, MissileID::Null, }, 3, 1, 12, 24 }, +/*SpellID::Magi*/ { P_("spell", "the Magi"), IS_CAST8, 10000, 20, 255, Magic | AllowedInTown, -1, 20, 45, { MissileID::Magi, MissileID::Null, }, 3, 1, 15, 30 }, +/*SpellID::Jester*/ { P_("spell", "the Jester"), IS_CAST8, 10000, 20, 255, Magic | Targeted, -1, 4, 30, { MissileID::Jester, MissileID::Null, }, 3, 1, 15, 30 }, +/*SpellID::LightningWall*/ { P_("spell", "Lightning Wall"), IS_CAST4, 600, 40, 28, Lightning | Targeted, 3, 2, 27, { MissileID::LightningWallControl, MissileID::Null, }, 2, 16, 8, 16 }, +/*SpellID::Immolation*/ { P_("spell", "Immolation"), IS_CAST2, 2100, 130, 60, Fire, 14, 10, 87, { MissileID::Immolation, MissileID::Null, }, 3, 35, 16, 32 }, +/*SpellID::Warp*/ { P_("spell", "Warp"), IS_CAST6, 300, 20, 35, Magic, 3, 3, 25, { MissileID::Warp, MissileID::Null, }, 3, 18, 8, 12 }, +/*SpellID::Reflect*/ { P_("spell", "Reflect"), IS_CAST6, 300, 20, 35, Magic, 3, 3, 25, { MissileID::Reflect, MissileID::Null, }, 3, 15, 8, 12 }, +/*SpellID::Berserk*/ { P_("spell", "Berserk"), IS_CAST6, 300, 20, 35, Magic | Targeted, 3, 3, 35, { MissileID::Berserk, MissileID::Null, }, 3, 15, 8, 12 }, +/*SpellID::RingOfFire*/ { P_("spell", "Ring of Fire"), IS_CAST2, 600, 40, 28, Fire, 5, 5, 27, { MissileID::RingOfFire, MissileID::Null, }, 2, 16, 8, 16 }, +/*SpellID::Search*/ { P_("spell", "Search"), IS_CAST6, 300, 20, 15, Magic, 1, 3, 25, { MissileID::Search, MissileID::Null, }, 1, 1, 8, 12 }, +/*SpellID::RuneOfFire*/ { P_("spell", "Rune of Fire"), IS_CAST8, 800, 30, 255, Magic | Targeted, -1, -1, 48, { MissileID::RuneOfFire, MissileID::Null, }, 1, 10, 40, 80 }, +/*SpellID::RuneOfLight*/ { P_("spell", "Rune of Light"), IS_CAST8, 800, 30, 255, Magic | Targeted, -1, -1, 48, { MissileID::RuneOfLight, MissileID::Null, }, 1, 10, 40, 80 }, +/*SpellID::RuneOfNova*/ { P_("spell", "Rune of Nova"), IS_CAST8, 800, 30, 255, Magic | Targeted, -1, -1, 48, { MissileID::RuneOfNova, MissileID::Null, }, 1, 10, 40, 80 }, +/*SpellID::RuneOfImmolation*/ { P_("spell", "Rune of Immolation"), IS_CAST8, 800, 30, 255, Magic | Targeted, -1, -1, 48, { MissileID::RuneOfImmolation, MissileID::Null, }, 1, 10, 40, 80 }, +/*SpellID::RuneOfStone*/ { P_("spell", "Rune of Stone"), IS_CAST8, 800, 30, 255, Magic | Targeted, -1, -1, 48, { MissileID::RuneOfStone, MissileID::Null, }, 1, 10, 40, 80 }, // clang-format on }; diff --git a/Source/spelldat.h b/Source/spelldat.h index 3d9cb8852..aec379e4d 100644 --- a/Source/spelldat.h +++ b/Source/spelldat.h @@ -6,8 +6,10 @@ #pragma once #include +#include #include "effects.h" +#include "utils/enum_traits.h" namespace devilution { @@ -203,24 +205,56 @@ enum class MissileID : int8_t { // clang-format on }; +enum class SpellDataFlags : uint8_t { + // The lower 2 bytes are used to store MagicType. + Fire = static_cast(MagicType::Fire), + Lightning = static_cast(MagicType::Lightning), + Magic = static_cast(MagicType::Magic), + Targeted = 1U << 2, + AllowedInTown = 1U << 3, +}; +use_enum_as_flags(SpellDataFlags); + struct SpellData { - SpellID sName; - uint8_t sManaCost; - MagicType sType; const char *sNameText; + _sfx_id sSFX; + uint16_t bookCost10; + uint8_t staffCost10; + uint8_t sManaCost; + SpellDataFlags flags; int8_t sBookLvl; int8_t sStaffLvl; - bool sTargeted; - bool sTownSpell; - int16_t sMinInt; - _sfx_id sSFX; - MissileID sMissiles[3]; + uint8_t minInt; + MissileID sMissiles[2]; uint8_t sManaAdj; uint8_t sMinMana; uint8_t sStaffMin; uint8_t sStaffMax; - uint32_t sBookCost; - uint16_t sStaffCost; + + [[nodiscard]] MagicType type() const + { + return static_cast(static_cast::type>(flags) & 0b11U); + } + + [[nodiscard]] uint32_t bookCost() const + { + return bookCost10 * 10; + } + + [[nodiscard]] uint16_t staffCost() const + { + return staffCost10 * 10; + } + + [[nodiscard]] bool isTargeted() const + { + return HasAnyOf(flags, SpellDataFlags::Targeted); + } + + [[nodiscard]] bool isAllowedInTown() const + { + return HasAnyOf(flags, SpellDataFlags::AllowedInTown); + } }; extern const SpellData SpellsData[]; diff --git a/Source/spells.cpp b/Source/spells.cpp index 2b11ba8c9..af90a3e5b 100644 --- a/Source/spells.cpp +++ b/Source/spells.cpp @@ -245,8 +245,9 @@ void CastSpell(int id, SpellID spl, int sx, int sy, int dx, int dy, int spllvl) } bool fizzled = false; - for (int i = 0; i < 3 && GetSpellData(spl).sMissiles[i] != MissileID::Null; i++) { - Missile *missile = AddMissile({ sx, sy }, { dx, dy }, dir, GetSpellData(spl).sMissiles[i], TARGET_MONSTERS, id, 0, spllvl); + const SpellData &spellData = GetSpellData(spl); + for (size_t i = 0; i < sizeof(spellData.sMissiles) / sizeof(spellData.sMissiles[0]) && spellData.sMissiles[i] != MissileID::Null; i++) { + Missile *missile = AddMissile({ sx, sy }, { dx, dy }, dir, spellData.sMissiles[i], TARGET_MONSTERS, id, 0, spllvl); fizzled |= (missile == nullptr); } if (spl == SpellID::ChargedBolt) { diff --git a/Source/stores.cpp b/Source/stores.cpp index bcae5e402..3571091af 100644 --- a/Source/stores.cpp +++ b/Source/stores.cpp @@ -752,7 +752,7 @@ void WitchBookLevel(Item &bookItem) { if (bookItem._iMiscId != IMISC_BOOK) return; - bookItem._iMinMag = GetSpellData(bookItem._iSpell).sMinInt; + bookItem._iMinMag = GetSpellData(bookItem._iSpell).minInt; int8_t spellLevel = MyPlayer->_pSplLvl[static_cast(bookItem._iSpell)]; while (spellLevel > 0) { bookItem._iMinMag += 20 * bookItem._iMinMag / 100; @@ -901,7 +901,7 @@ bool WitchRechargeOk(int i) void AddStoreHoldRecharge(Item itm, int8_t i) { storehold[storenumh] = itm; - storehold[storenumh]._ivalue += GetSpellData(itm._iSpell).sStaffCost; + storehold[storenumh]._ivalue += GetSpellData(itm._iSpell).staffCost(); storehold[storenumh]._ivalue = storehold[storenumh]._ivalue * (storehold[storenumh]._iMaxCharges - storehold[storenumh]._iCharges) / (storehold[storenumh]._iMaxCharges * 2); storehold[storenumh]._iIvalue = storehold[storenumh]._ivalue; storehidx[storenumh] = i;