|
|
|
|
#include "panels/spell_book.hpp"
|
|
|
|
|
|
|
|
|
|
#include <fmt/format.h>
|
|
|
|
|
|
|
|
|
|
#include "control.h"
|
|
|
|
|
#include "engine/cel_sprite.hpp"
|
|
|
|
|
#include "engine/load_cel.hpp"
|
|
|
|
|
#include "engine/rectangle.hpp"
|
|
|
|
|
#include "engine/render/cel_render.hpp"
|
|
|
|
|
#include "engine/render/text_render.hpp"
|
|
|
|
|
#include "init.h"
|
|
|
|
|
#include "missiles.h"
|
|
|
|
|
#include "panels/spell_icons.hpp"
|
|
|
|
|
#include "panels/ui_panels.hpp"
|
|
|
|
|
#include "player.h"
|
|
|
|
|
#include "spelldat.h"
|
|
|
|
|
#include "utils/language.h"
|
|
|
|
|
#include "utils/stdcompat/optional.hpp"
|
|
|
|
|
|
|
|
|
|
#define SPLICONLAST (gbIsHellfire ? 51 : 42)
|
|
|
|
|
|
|
|
|
|
namespace devilution {
|
|
|
|
|
|
|
|
|
|
std::optional<OwnedCelSprite> pSBkIconCels;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
std::optional<OwnedCelSprite> pSBkBtnCel;
|
|
|
|
|
std::optional<OwnedCelSprite> pSpellBkCel;
|
|
|
|
|
|
|
|
|
|
/** Maps from spellbook page number and position to spell_id. */
|
|
|
|
|
spell_id SpellPages[6][7] = {
|
|
|
|
|
{ SPL_NULL, SPL_FIREBOLT, SPL_CBOLT, SPL_HBOLT, SPL_HEAL, SPL_HEALOTHER, SPL_FLAME },
|
|
|
|
|
{ SPL_RESURRECT, SPL_FIREWALL, SPL_TELEKINESIS, SPL_LIGHTNING, SPL_TOWN, SPL_FLASH, SPL_STONE },
|
|
|
|
|
{ SPL_RNDTELEPORT, SPL_MANASHIELD, SPL_ELEMENT, SPL_FIREBALL, SPL_WAVE, SPL_CHAIN, SPL_GUARDIAN },
|
|
|
|
|
{ SPL_NOVA, SPL_GOLEM, SPL_TELEPORT, SPL_APOCA, SPL_BONESPIRIT, SPL_FLARE, SPL_ETHEREALIZE },
|
|
|
|
|
{ SPL_LIGHTWALL, SPL_IMMOLAT, SPL_WARP, SPL_REFLECT, SPL_BERSERK, SPL_FIRERING, SPL_SEARCH },
|
|
|
|
|
{ SPL_INVALID, SPL_INVALID, SPL_INVALID, SPL_INVALID, SPL_INVALID, SPL_INVALID, SPL_INVALID }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
constexpr int SpellBookDescriptionWidth = 250;
|
|
|
|
|
constexpr int SpellBookDescriptionHeight = 43;
|
|
|
|
|
constexpr int SpellBookDescriptionPaddingLeft = 2;
|
|
|
|
|
constexpr int SpellBookDescriptionPaddingRight = 2;
|
|
|
|
|
|
|
|
|
|
void PrintSBookStr(const Surface &out, Point position, string_view text, UiFlags flags = UiFlags::None)
|
|
|
|
|
{
|
|
|
|
|
DrawString(out, text,
|
|
|
|
|
{ GetPanelPosition(UiPanels::Spell, { SPLICONLENGTH + SpellBookDescriptionPaddingLeft + position.x, position.y }),
|
|
|
|
|
{ SpellBookDescriptionWidth - SpellBookDescriptionPaddingLeft - SpellBookDescriptionPaddingRight, 0 } },
|
|
|
|
|
UiFlags::ColorWhite | flags);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
spell_type GetSBookTrans(spell_id ii, bool townok)
|
|
|
|
|
{
|
|
|
|
|
Player &player = *MyPlayer;
|
|
|
|
|
if ((player._pClass == HeroClass::Monk) && (ii == SPL_SEARCH))
|
|
|
|
|
return RSPLTYPE_SKILL;
|
|
|
|
|
spell_type st = RSPLTYPE_SPELL;
|
|
|
|
|
if ((player._pISpells & GetSpellBitmask(ii)) != 0) {
|
|
|
|
|
st = RSPLTYPE_CHARGES;
|
|
|
|
|
}
|
|
|
|
|
if ((player._pAblSpells & GetSpellBitmask(ii)) != 0) {
|
|
|
|
|
st = RSPLTYPE_SKILL;
|
|
|
|
|
}
|
|
|
|
|
if (st == RSPLTYPE_SPELL) {
|
|
|
|
|
if (CheckSpell(MyPlayerId, ii, st, true) != SpellCheckResult::Success) {
|
|
|
|
|
st = RSPLTYPE_INVALID;
|
|
|
|
|
}
|
|
|
|
|
if ((char)(player._pSplLvl[ii] + player._pISplLvlAdd) <= 0) {
|
|
|
|
|
st = RSPLTYPE_INVALID;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (townok && leveltype == DTYPE_TOWN && st != RSPLTYPE_INVALID && !spelldata[ii].sTownSpell) {
|
|
|
|
|
st = RSPLTYPE_INVALID;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return st;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
void InitSpellBook()
|
|
|
|
|
{
|
|
|
|
|
pSpellBkCel = LoadCel("Data\\SpellBk.CEL", SPANEL_WIDTH);
|
|
|
|
|
|
|
|
|
|
if (gbIsHellfire) {
|
|
|
|
|
static const uint16_t SBkBtnHellfireWidths[] = { 61, 61, 61, 61, 61, 76 };
|
|
|
|
|
pSBkBtnCel = LoadCel("Data\\SpellBkB.CEL", SBkBtnHellfireWidths);
|
|
|
|
|
} else {
|
|
|
|
|
pSBkBtnCel = LoadCel("Data\\SpellBkB.CEL", 76);
|
|
|
|
|
}
|
|
|
|
|
pSBkIconCels = LoadCel("Data\\SpellI2.CEL", 37);
|
|
|
|
|
|
|
|
|
|
Player &player = *MyPlayer;
|
|
|
|
|
if (player._pClass == HeroClass::Warrior) {
|
|
|
|
|
SpellPages[0][0] = SPL_REPAIR;
|
|
|
|
|
} else if (player._pClass == HeroClass::Rogue) {
|
|
|
|
|
SpellPages[0][0] = SPL_DISARM;
|
|
|
|
|
} else if (player._pClass == HeroClass::Sorcerer) {
|
|
|
|
|
SpellPages[0][0] = SPL_RECHARGE;
|
|
|
|
|
} else if (player._pClass == HeroClass::Monk) {
|
|
|
|
|
SpellPages[0][0] = SPL_SEARCH;
|
|
|
|
|
} else if (player._pClass == HeroClass::Bard) {
|
|
|
|
|
SpellPages[0][0] = SPL_IDENTIFY;
|
|
|
|
|
} else if (player._pClass == HeroClass::Barbarian) {
|
|
|
|
|
SpellPages[0][0] = SPL_BLODBOIL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FreeSpellBook()
|
|
|
|
|
{
|
|
|
|
|
pSpellBkCel = std::nullopt;
|
|
|
|
|
pSBkBtnCel = std::nullopt;
|
|
|
|
|
pSBkIconCels = std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DrawSpellBook(const Surface &out)
|
|
|
|
|
{
|
|
|
|
|
CelDrawTo(out, GetPanelPosition(UiPanels::Spell, { 0, 351 }), *pSpellBkCel, 0);
|
|
|
|
|
if (gbIsHellfire && sbooktab < 5) {
|
|
|
|
|
CelDrawTo(out, GetPanelPosition(UiPanels::Spell, { 61 * sbooktab + 7, 348 }), *pSBkBtnCel, sbooktab);
|
|
|
|
|
} else {
|
|
|
|
|
// BUGFIX: rendering of page 3 and page 4 buttons are both off-by-one pixel (fixed).
|
|
|
|
|
int sx = 76 * sbooktab + 7;
|
|
|
|
|
if (sbooktab == 2 || sbooktab == 3) {
|
|
|
|
|
sx++;
|
|
|
|
|
}
|
|
|
|
|
CelDrawTo(out, GetPanelPosition(UiPanels::Spell, { sx, 348 }), *pSBkBtnCel, sbooktab);
|
|
|
|
|
}
|
|
|
|
|
Player &player = *MyPlayer;
|
|
|
|
|
uint64_t spl = player._pMemSpells | player._pISpells | player._pAblSpells;
|
|
|
|
|
|
|
|
|
|
const int lineHeight = 18;
|
|
|
|
|
|
|
|
|
|
int yp = 12;
|
|
|
|
|
const int textPaddingTop = 7;
|
|
|
|
|
for (int i = 1; i < 8; i++) {
|
|
|
|
|
spell_id sn = SpellPages[sbooktab][i - 1];
|
|
|
|
|
if (sn != SPL_INVALID && (spl & GetSpellBitmask(sn)) != 0) {
|
|
|
|
|
spell_type st = GetSBookTrans(sn, true);
|
|
|
|
|
SetSpellTrans(st);
|
|
|
|
|
const Point spellCellPosition = GetPanelPosition(UiPanels::Spell, { 11, yp + SpellBookDescriptionHeight });
|
|
|
|
|
DrawSpellCel(out, spellCellPosition, *pSBkIconCels, SpellITbl[sn]);
|
|
|
|
|
if (sn == player._pRSpell && st == player._pRSplType) {
|
|
|
|
|
SetSpellTrans(RSPLTYPE_SKILL);
|
|
|
|
|
DrawSpellCel(out, spellCellPosition, *pSBkIconCels, SPLICONLAST);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Point line0 { 0, yp + textPaddingTop };
|
|
|
|
|
const Point line1 { 0, yp + textPaddingTop + lineHeight };
|
|
|
|
|
PrintSBookStr(out, line0, pgettext("spell", spelldata[sn].sNameText));
|
|
|
|
|
switch (GetSBookTrans(sn, false)) {
|
|
|
|
|
case RSPLTYPE_SKILL:
|
|
|
|
|
PrintSBookStr(out, line1, _("Skill"));
|
|
|
|
|
break;
|
|
|
|
|
case RSPLTYPE_CHARGES: {
|
|
|
|
|
int charges = player.InvBody[INVLOC_HAND_LEFT]._iCharges;
|
|
|
|
|
PrintSBookStr(out, line1, fmt::format(ngettext("Staff ({:d} charge)", "Staff ({:d} charges)", charges), charges));
|
|
|
|
|
} break;
|
|
|
|
|
default: {
|
|
|
|
|
int mana = GetManaAmount(player, sn) >> 6;
|
|
|
|
|
int lvl = std::max(player._pSplLvl[sn] + player._pISplLvlAdd, 0);
|
|
|
|
|
PrintSBookStr(out, line0, fmt::format(pgettext(/* TRANSLATORS: UI constraints, keep short please.*/ "spellbook", "Level {:d}"), lvl), UiFlags::AlignRight);
|
|
|
|
|
if (lvl == 0) {
|
|
|
|
|
PrintSBookStr(out, line1, _("Unusable"), UiFlags::AlignRight);
|
|
|
|
|
} else {
|
|
|
|
|
if (sn != SPL_BONESPIRIT) {
|
|
|
|
|
int min;
|
|
|
|
|
int max;
|
|
|
|
|
GetDamageAmt(sn, &min, &max);
|
|
|
|
|
if (min != -1) {
|
|
|
|
|
if (sn == SPL_HEAL || sn == SPL_HEALOTHER) {
|
|
|
|
|
PrintSBookStr(out, line1, fmt::format(_(/* TRANSLATORS: UI constraints, keep short please.*/ "Heals: {:d} - {:d}"), min, max), UiFlags::AlignRight);
|
|
|
|
|
} else {
|
|
|
|
|
PrintSBookStr(out, line1, fmt::format(_(/* TRANSLATORS: UI constraints, keep short please.*/ "Damage: {:d} - {:d}"), min, max), UiFlags::AlignRight);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
PrintSBookStr(out, line1, _(/* TRANSLATORS: UI constraints, keep short please.*/ "Dmg: 1/3 target hp"), UiFlags::AlignRight);
|
|
|
|
|
}
|
|
|
|
|
PrintSBookStr(out, line1, fmt::format(pgettext(/* TRANSLATORS: UI constraints, keep short please.*/ "spellbook", "Mana: {:d}"), mana));
|
|
|
|
|
}
|
|
|
|
|
} break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
yp += SpellBookDescriptionHeight;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CheckSBook()
|
|
|
|
|
{
|
|
|
|
|
Rectangle iconArea = { GetPanelPosition(UiPanels::Spell, { 11, 18 }), { 48 - 11, 314 - 18 } };
|
|
|
|
|
Rectangle tabArea = { GetPanelPosition(UiPanels::Spell, { 7, 320 }), { 311 - 7, 349 - 320 } };
|
|
|
|
|
if (iconArea.Contains(MousePosition)) {
|
|
|
|
|
spell_id sn = SpellPages[sbooktab][(MousePosition.y - GetRightPanel().position.y - 18) / 43];
|
|
|
|
|
Player &player = *MyPlayer;
|
|
|
|
|
uint64_t spl = player._pMemSpells | player._pISpells | player._pAblSpells;
|
|
|
|
|
if (sn != SPL_INVALID && (spl & GetSpellBitmask(sn)) != 0) {
|
|
|
|
|
spell_type st = RSPLTYPE_SPELL;
|
|
|
|
|
if ((player._pISpells & GetSpellBitmask(sn)) != 0) {
|
|
|
|
|
st = RSPLTYPE_CHARGES;
|
|
|
|
|
}
|
|
|
|
|
if ((player._pAblSpells & GetSpellBitmask(sn)) != 0) {
|
|
|
|
|
st = RSPLTYPE_SKILL;
|
|
|
|
|
}
|
|
|
|
|
player._pRSpell = sn;
|
|
|
|
|
player._pRSplType = st;
|
|
|
|
|
force_redraw = 255;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (tabArea.Contains(MousePosition)) {
|
|
|
|
|
sbooktab = (MousePosition.x - (GetRightPanel().position.x + 7)) / (gbIsHellfire ? 61 : 76);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace devilution
|