Browse Source

Optimize `SpellData` size: 40 bytes -> 24

pull/5784/head
Gleb Mazovetskiy 3 years ago
parent
commit
ebcd6b222d
  1. 2
      Source/controls/modifier_hints.cpp
  2. 8
      Source/controls/plrctrls.cpp
  3. 4
      Source/inv.cpp
  4. 2
      Source/itemdat.h
  5. 30
      Source/items.cpp
  6. 14
      Source/msg.cpp
  7. 2
      Source/panels/spell_book.cpp
  8. 4
      Source/panels/spell_list.cpp
  9. 14
      Source/player.cpp
  10. 114
      Source/spelldat.cpp
  11. 54
      Source/spelldat.h
  12. 5
      Source/spells.cpp
  13. 4
      Source/stores.cpp

2
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;

8
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);
}
}

4
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;
}

2
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;
};

30
Source/items.cpp

@ -613,7 +613,7 @@ void GetBookSpell(Item &item, int lvl)
lvl = 5;
int s = static_cast<int8_t>(SpellID::Firebolt);
enum SpellID bs = SpellID::Firebolt;
SpellID bs = SpellID::Firebolt;
while (rv > 0) {
int sLevel = GetSpellBookLevel(static_cast<SpellID>(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<int8_t>(SpellID::Firebolt);
enum SpellID bs = SpellID::Null;
SpellID bs = SpellID::Null;
while (rv > 0) {
int sLevel = GetSpellStaffLevel(static_cast<SpellID>(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<int8_t>(_iSpell)];
while (spellLevel != 0) {
_iMinMag += 20 * _iMinMag / 100;

14
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<SpellID>(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<SpellID>(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<SpellID>(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);
}

2
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;
}

4
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) {

14
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;
}

114
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
};

54
Source/spelldat.h

@ -6,8 +6,10 @@
#pragma once
#include <cstdint>
#include <type_traits>
#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<uint8_t>(MagicType::Fire),
Lightning = static_cast<uint8_t>(MagicType::Lightning),
Magic = static_cast<uint8_t>(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<MagicType>(static_cast<std::underlying_type<SpellDataFlags>::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[];

5
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) {

4
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<int8_t>(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;

Loading…
Cancel
Save