You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
217 lines
7.9 KiB
217 lines
7.9 KiB
#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(fmt::runtime(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(fmt::runtime(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(fmt::runtime(_(/* TRANSLATORS: UI constraints, keep short please.*/ "Heals: {:d} - {:d}")), min, max), UiFlags::AlignRight); |
|
} else { |
|
PrintSBookStr(out, line1, fmt::format(fmt::runtime(_(/* 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(fmt::runtime(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
|
|
|