Browse Source
Moves the spell list/book UI from `control.cpp` code into separate files.pull/3586/head
14 changed files with 1561 additions and 676 deletions
@ -0,0 +1,212 @@
|
||||
#include "panels/spell_book.hpp" |
||||
|
||||
#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 "player.h" |
||||
#include "spelldat.h" |
||||
#include "utils/language.h" |
||||
#include "utils/stdcompat/optional.hpp" |
||||
|
||||
#define SPLICONLAST (gbIsHellfire ? 52 : 43) |
||||
|
||||
namespace devilution { |
||||
namespace { |
||||
|
||||
std::optional<CelSprite> pSBkBtnCel; |
||||
std::optional<CelSprite> pSBkIconCels; |
||||
std::optional<CelSprite> 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) |
||||
{ |
||||
auto &player = Players[MyPlayerId]; |
||||
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 && currlevel == 0 && 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 int SBkBtnHellfireWidths[] = { 0, 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 = Players[MyPlayerId]; |
||||
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, 1); |
||||
if (gbIsHellfire && sbooktab < 5) { |
||||
CelDrawTo(out, GetPanelPosition(UiPanels::Spell, { 61 * sbooktab + 7, 348 }), *pSBkBtnCel, sbooktab + 1); |
||||
} 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 + 1); |
||||
} |
||||
auto &player = Players[MyPlayerId]; |
||||
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; |
||||
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 constrains, keep short please.*/ "Heals: {:d} - {:d}"), min, max), UiFlags::AlignRight); |
||||
} else { |
||||
PrintSBookStr(out, line1, fmt::format(_(/* TRANSLATORS: UI constrains, keep short please.*/ "Damage: {:d} - {:d}"), min, max), UiFlags::AlignRight); |
||||
} |
||||
} |
||||
} else { |
||||
PrintSBookStr(out, line1, _(/* TRANSLATORS: UI constrains, keep short please.*/ "Dmg: 1/3 target hp"), UiFlags::AlignRight); |
||||
} |
||||
PrintSBookStr(out, line1, fmt::format(pgettext(/* TRANSLATORS: UI constrains, keep short please.*/ "spellbook", "Mana: {:d}"), mana)); |
||||
int lvl = std::max(player._pSplLvl[sn] + player._pISplLvlAdd, 0); |
||||
if (lvl == 0) { |
||||
PrintSBookStr(out, line0, _("Level 0 - Unusable"), UiFlags::AlignRight); |
||||
} else { |
||||
PrintSBookStr(out, line0, fmt::format(pgettext(/* TRANSLATORS: UI constrains, keep short please.*/ "spellbook", "Level {:d}"), lvl), UiFlags::AlignRight); |
||||
} |
||||
} 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]; |
||||
auto &player = Players[MyPlayerId]; |
||||
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
|
||||
@ -0,0 +1,12 @@
|
||||
#pragma once |
||||
|
||||
#include "engine/surface.hpp" |
||||
|
||||
namespace devilution { |
||||
|
||||
void InitSpellBook(); |
||||
void FreeSpellBook(); |
||||
void CheckSBook(); |
||||
void DrawSpellBook(const Surface &out); |
||||
|
||||
} // namespace devilution
|
||||
@ -0,0 +1,152 @@
|
||||
#include "panels/spell_icons.hpp" |
||||
|
||||
#include "engine/load_cel.hpp" |
||||
#include "engine/render/cel_render.hpp" |
||||
#include "init.h" |
||||
#include "palette.h" |
||||
#include "utils/stdcompat/optional.hpp" |
||||
|
||||
namespace devilution { |
||||
|
||||
namespace { |
||||
std::optional<CelSprite> pSpellCels; |
||||
uint8_t SplTransTbl[256]; |
||||
} // namespace
|
||||
|
||||
const char SpellITbl[] = { |
||||
27, |
||||
1, |
||||
2, |
||||
3, |
||||
4, |
||||
5, |
||||
6, |
||||
7, |
||||
8, |
||||
9, |
||||
28, |
||||
13, |
||||
12, |
||||
18, |
||||
16, |
||||
14, |
||||
18, |
||||
19, |
||||
11, |
||||
20, |
||||
15, |
||||
21, |
||||
23, |
||||
24, |
||||
25, |
||||
22, |
||||
26, |
||||
29, |
||||
37, |
||||
38, |
||||
39, |
||||
42, |
||||
41, |
||||
40, |
||||
10, |
||||
36, |
||||
30, |
||||
51, |
||||
51, |
||||
50, |
||||
46, |
||||
47, |
||||
43, |
||||
45, |
||||
48, |
||||
49, |
||||
44, |
||||
35, |
||||
35, |
||||
35, |
||||
35, |
||||
35, |
||||
}; |
||||
|
||||
void LoadSpellIcons() |
||||
{ |
||||
if (!gbIsHellfire) |
||||
pSpellCels = LoadCel("CtrlPan\\SpelIcon.CEL", SPLICONLENGTH); |
||||
else |
||||
pSpellCels = LoadCel("Data\\SpelIcon.CEL", SPLICONLENGTH); |
||||
SetSpellTrans(RSPLTYPE_SKILL); |
||||
} |
||||
|
||||
void FreeSpellIcons() |
||||
{ |
||||
pSpellCels = std::nullopt; |
||||
} |
||||
|
||||
void DrawSpellCel(const Surface &out, Point position, int nCel) |
||||
{ |
||||
DrawSpellCel(out, position, *pSpellCels, nCel); |
||||
} |
||||
|
||||
void DrawSpellCel(const Surface &out, Point position, const CelSprite &sprite, int nCel) |
||||
{ |
||||
CelDrawLightTo(out, position, sprite, nCel, SplTransTbl); |
||||
} |
||||
|
||||
void SetSpellTrans(spell_type t) |
||||
{ |
||||
if (t == RSPLTYPE_SKILL) { |
||||
for (int i = 0; i < 128; i++) |
||||
SplTransTbl[i] = i; |
||||
} |
||||
for (int i = 128; i < 256; i++) |
||||
SplTransTbl[i] = i; |
||||
SplTransTbl[255] = 0; |
||||
|
||||
switch (t) { |
||||
case RSPLTYPE_SPELL: |
||||
SplTransTbl[PAL8_YELLOW] = PAL16_BLUE + 1; |
||||
SplTransTbl[PAL8_YELLOW + 1] = PAL16_BLUE + 3; |
||||
SplTransTbl[PAL8_YELLOW + 2] = PAL16_BLUE + 5; |
||||
for (int i = PAL16_BLUE; i < PAL16_BLUE + 16; i++) { |
||||
SplTransTbl[PAL16_BEIGE - PAL16_BLUE + i] = i; |
||||
SplTransTbl[PAL16_YELLOW - PAL16_BLUE + i] = i; |
||||
SplTransTbl[PAL16_ORANGE - PAL16_BLUE + i] = i; |
||||
} |
||||
break; |
||||
case RSPLTYPE_SCROLL: |
||||
SplTransTbl[PAL8_YELLOW] = PAL16_BEIGE + 1; |
||||
SplTransTbl[PAL8_YELLOW + 1] = PAL16_BEIGE + 3; |
||||
SplTransTbl[PAL8_YELLOW + 2] = PAL16_BEIGE + 5; |
||||
for (int i = PAL16_BEIGE; i < PAL16_BEIGE + 16; i++) { |
||||
SplTransTbl[PAL16_YELLOW - PAL16_BEIGE + i] = i; |
||||
SplTransTbl[PAL16_ORANGE - PAL16_BEIGE + i] = i; |
||||
} |
||||
break; |
||||
case RSPLTYPE_CHARGES: |
||||
SplTransTbl[PAL8_YELLOW] = PAL16_ORANGE + 1; |
||||
SplTransTbl[PAL8_YELLOW + 1] = PAL16_ORANGE + 3; |
||||
SplTransTbl[PAL8_YELLOW + 2] = PAL16_ORANGE + 5; |
||||
for (int i = PAL16_ORANGE; i < PAL16_ORANGE + 16; i++) { |
||||
SplTransTbl[PAL16_BEIGE - PAL16_ORANGE + i] = i; |
||||
SplTransTbl[PAL16_YELLOW - PAL16_ORANGE + i] = i; |
||||
} |
||||
break; |
||||
case RSPLTYPE_INVALID: |
||||
SplTransTbl[PAL8_YELLOW] = PAL16_GRAY + 1; |
||||
SplTransTbl[PAL8_YELLOW + 1] = PAL16_GRAY + 3; |
||||
SplTransTbl[PAL8_YELLOW + 2] = PAL16_GRAY + 5; |
||||
for (int i = PAL16_GRAY; i < PAL16_GRAY + 15; i++) { |
||||
SplTransTbl[PAL16_BEIGE - PAL16_GRAY + i] = i; |
||||
SplTransTbl[PAL16_YELLOW - PAL16_GRAY + i] = i; |
||||
SplTransTbl[PAL16_ORANGE - PAL16_GRAY + i] = i; |
||||
} |
||||
SplTransTbl[PAL16_BEIGE + 15] = 0; |
||||
SplTransTbl[PAL16_YELLOW + 15] = 0; |
||||
SplTransTbl[PAL16_ORANGE + 15] = 0; |
||||
break; |
||||
case RSPLTYPE_SKILL: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
} // namespace devilution
|
||||
@ -0,0 +1,37 @@
|
||||
#pragma once |
||||
|
||||
#include "engine/cel_sprite.hpp" |
||||
#include "engine/point.hpp" |
||||
#include "engine/surface.hpp" |
||||
#include "spelldat.h" |
||||
|
||||
#define SPLICONLENGTH 56 |
||||
|
||||
namespace devilution { |
||||
|
||||
/** Maps from spell_id to spelicon.cel frame number. */ |
||||
extern const char SpellITbl[]; |
||||
|
||||
/**
|
||||
* Draw spell icon onto the given buffer. |
||||
* @param out Output buffer. |
||||
* @param position Buffer coordinates. |
||||
* @param nCel Index of the cel frame to draw. 0 based. |
||||
*/ |
||||
void DrawSpellCel(const Surface &out, Point position, int nCel); |
||||
|
||||
/**
|
||||
* Draw spell icon onto the given buffer. |
||||
* @param out Output buffer. |
||||
* @param position Buffer coordinates. |
||||
* @param sprite Icons sprite sheet. |
||||
* @param nCel Index of the cel frame to draw. 0 based. |
||||
*/ |
||||
void DrawSpellCel(const Surface &out, Point position, const CelSprite &sprite, int nCel); |
||||
|
||||
void SetSpellTrans(spell_type t); |
||||
|
||||
void LoadSpellIcons(); |
||||
void FreeSpellIcons(); |
||||
|
||||
} // namespace devilution
|
||||
@ -0,0 +1,372 @@
|
||||
#include "panels/spell_list.hpp" |
||||
|
||||
#include "control.h" |
||||
#include "controls/keymapper.hpp" |
||||
#include "engine.h" |
||||
#include "engine/render/text_render.hpp" |
||||
#include "inv_iterators.hpp" |
||||
#include "palette.h" |
||||
#include "panels/spell_icons.hpp" |
||||
#include "player.h" |
||||
#include "spells.h" |
||||
#include "utils/language.h" |
||||
|
||||
#define SPLROWICONLS 10 |
||||
|
||||
namespace devilution { |
||||
|
||||
extern std::array<Keymapper::ActionIndex, 4> quickSpellActionIndexes; |
||||
|
||||
namespace { |
||||
|
||||
void PrintSBookSpellType(const Surface &out, Point position, const std::string &text, uint8_t rectColorIndex) |
||||
{ |
||||
Point rect { position }; |
||||
rect += Displacement { 0, -SPLICONLENGTH + 1 }; |
||||
|
||||
// Top
|
||||
DrawHorizontalLine(out, rect, SPLICONLENGTH, rectColorIndex); |
||||
DrawHorizontalLine(out, rect + Displacement { 0, 1 }, SPLICONLENGTH, rectColorIndex); |
||||
|
||||
// Bottom
|
||||
DrawHorizontalLine(out, rect + Displacement { 0, SPLICONLENGTH - 2 }, SPLICONLENGTH, rectColorIndex); |
||||
DrawHorizontalLine(out, rect + Displacement { 0, SPLICONLENGTH - 1 }, SPLICONLENGTH, rectColorIndex); |
||||
|
||||
// Left Side
|
||||
DrawVerticalLine(out, rect, SPLICONLENGTH, rectColorIndex); |
||||
DrawVerticalLine(out, rect + Displacement { 1, 0 }, SPLICONLENGTH, rectColorIndex); |
||||
|
||||
// Right Side
|
||||
DrawVerticalLine(out, rect + Displacement { SPLICONLENGTH - 2, 0 }, SPLICONLENGTH, rectColorIndex); |
||||
DrawVerticalLine(out, rect + Displacement { SPLICONLENGTH - 1, 0 }, SPLICONLENGTH, rectColorIndex); |
||||
|
||||
// Align the spell type text with bottom of spell icon
|
||||
position += Displacement { SPLICONLENGTH / 2 - GetLineWidth(text.c_str()) / 2, (IsSmallFontTall() ? -19 : -15) }; |
||||
|
||||
// Draw a drop shadow below and to the left of the text
|
||||
DrawString(out, text, position + Displacement { -1, 1 }, UiFlags::ColorBlack); |
||||
DrawString(out, text, position + Displacement { -1, -1 }, UiFlags::ColorBlack); |
||||
DrawString(out, text, position + Displacement { 1, -1 }, UiFlags::ColorBlack); |
||||
// Then draw the text over the top
|
||||
DrawString(out, text, position, UiFlags::ColorWhite); |
||||
} |
||||
|
||||
void PrintSBookHotkey(const Surface &out, Point position, const std::string &text) |
||||
{ |
||||
// Align the hot key text with the top-right corner of the spell icon
|
||||
position += Displacement { SPLICONLENGTH - (GetLineWidth(text.c_str()) + 5), 5 - SPLICONLENGTH }; |
||||
|
||||
// Draw a drop shadow below and to the left of the text
|
||||
DrawString(out, text, position + Displacement { -1, 1 }, UiFlags::ColorBlack); |
||||
// Then draw the text over the top
|
||||
DrawString(out, text, position, UiFlags::ColorWhite); |
||||
} |
||||
|
||||
bool GetSpellListSelection(spell_id &pSpell, spell_type &pSplType) |
||||
{ |
||||
pSpell = spell_id::SPL_INVALID; |
||||
pSplType = spell_type::RSPLTYPE_INVALID; |
||||
auto &myPlayer = Players[MyPlayerId]; |
||||
|
||||
for (auto &spellListItem : GetSpellListItems()) { |
||||
if (spellListItem.isSelected) { |
||||
pSpell = spellListItem.id; |
||||
pSplType = spellListItem.type; |
||||
if (myPlayer._pClass == HeroClass::Monk && spellListItem.id == SPL_SEARCH) |
||||
pSplType = RSPLTYPE_SKILL; |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
void DrawSpell(const Surface &out) |
||||
{ |
||||
auto &myPlayer = Players[MyPlayerId]; |
||||
spell_id spl = myPlayer._pRSpell; |
||||
spell_type st = myPlayer._pRSplType; |
||||
|
||||
// BUGFIX: Move the next line into the if statement to avoid OOB (SPL_INVALID is -1) (fixed)
|
||||
if (st == RSPLTYPE_SPELL && spl != SPL_INVALID) { |
||||
int tlvl = myPlayer._pISplLvlAdd + myPlayer._pSplLvl[spl]; |
||||
if (CheckSpell(MyPlayerId, spl, st, true) != SpellCheckResult::Success) |
||||
st = RSPLTYPE_INVALID; |
||||
if (tlvl <= 0) |
||||
st = RSPLTYPE_INVALID; |
||||
} |
||||
if (currlevel == 0 && st != RSPLTYPE_INVALID && !spelldata[spl].sTownSpell) |
||||
st = RSPLTYPE_INVALID; |
||||
SetSpellTrans(st); |
||||
const int nCel = (spl != SPL_INVALID) ? SpellITbl[spl] : 27; |
||||
const Point position { PANEL_X + 565, PANEL_Y + 119 }; |
||||
DrawSpellCel(out, position, nCel); |
||||
} |
||||
|
||||
void DrawSpellList(const Surface &out) |
||||
{ |
||||
infostr[0] = '\0'; |
||||
ClearPanel(); |
||||
|
||||
auto &myPlayer = Players[MyPlayerId]; |
||||
|
||||
for (auto &spellListItem : GetSpellListItems()) { |
||||
const spell_id spellId = spellListItem.id; |
||||
spell_type transType = spellListItem.type; |
||||
int spellLevel = 0; |
||||
const SpellData &spellDataItem = spelldata[static_cast<size_t>(spellListItem.id)]; |
||||
if (currlevel == 0 && !spellDataItem.sTownSpell) { |
||||
transType = RSPLTYPE_INVALID; |
||||
} |
||||
if (spellListItem.type == RSPLTYPE_SPELL) { |
||||
spellLevel = std::max(myPlayer._pISplLvlAdd + myPlayer._pSplLvl[spellListItem.id], 0); |
||||
if (spellLevel == 0) |
||||
transType = RSPLTYPE_INVALID; |
||||
} |
||||
|
||||
SetSpellTrans(transType); |
||||
DrawSpellCel(out, spellListItem.location, SpellITbl[static_cast<size_t>(spellId)]); |
||||
|
||||
if (!spellListItem.isSelected) |
||||
continue; |
||||
|
||||
uint8_t spellColor = PAL16_GRAY + 5; |
||||
|
||||
switch (spellListItem.type) { |
||||
case RSPLTYPE_SKILL: |
||||
spellColor = PAL16_YELLOW - 46; |
||||
PrintSBookSpellType(out, spellListItem.location, _("Skill"), spellColor); |
||||
strcpy(infostr, fmt::format(_("{:s} Skill"), pgettext("spell", spellDataItem.sSkillText)).c_str()); |
||||
break; |
||||
case RSPLTYPE_SPELL: |
||||
if (myPlayer.plrlevel != 0) { |
||||
spellColor = PAL16_BLUE + 5; |
||||
} |
||||
PrintSBookSpellType(out, spellListItem.location, _("Spell"), spellColor); |
||||
strcpy(infostr, fmt::format(_("{:s} Spell"), pgettext("spell", spellDataItem.sNameText)).c_str()); |
||||
if (spellId == SPL_HBOLT) { |
||||
strcpy(tempstr, _("Damages undead only")); |
||||
AddPanelString(tempstr); |
||||
} |
||||
if (spellLevel == 0) |
||||
strcpy(tempstr, _("Spell Level 0 - Unusable")); |
||||
else |
||||
strcpy(tempstr, fmt::format(_("Spell Level {:d}"), spellLevel).c_str()); |
||||
AddPanelString(tempstr); |
||||
break; |
||||
case RSPLTYPE_SCROLL: { |
||||
if (myPlayer.plrlevel != 0) { |
||||
spellColor = PAL16_RED - 59; |
||||
} |
||||
PrintSBookSpellType(out, spellListItem.location, _("Scroll"), spellColor); |
||||
strcpy(infostr, fmt::format(_("Scroll of {:s}"), pgettext("spell", spellDataItem.sNameText)).c_str()); |
||||
const InventoryAndBeltPlayerItemsRange items { myPlayer }; |
||||
const int scrollCount = std::count_if(items.begin(), items.end(), [spellId](const Item &item) { |
||||
return item.IsScrollOf(spellId); |
||||
}); |
||||
strcpy(tempstr, fmt::format(ngettext("{:d} Scroll", "{:d} Scrolls", scrollCount), scrollCount).c_str()); |
||||
AddPanelString(tempstr); |
||||
} break; |
||||
case RSPLTYPE_CHARGES: { |
||||
if (myPlayer.plrlevel != 0) { |
||||
spellColor = PAL16_ORANGE + 5; |
||||
} |
||||
PrintSBookSpellType(out, spellListItem.location, _("Staff"), spellColor); |
||||
strcpy(infostr, fmt::format(_("Staff of {:s}"), pgettext("spell", spellDataItem.sNameText)).c_str()); |
||||
int charges = myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges; |
||||
strcpy(tempstr, fmt::format(ngettext("{:d} Charge", "{:d} Charges", charges), charges).c_str()); |
||||
AddPanelString(tempstr); |
||||
} break; |
||||
case RSPLTYPE_INVALID: |
||||
break; |
||||
} |
||||
for (int t = 0; t < 4; t++) { |
||||
if (myPlayer._pSplHotKey[t] == spellId && myPlayer._pSplTHotKey[t] == spellListItem.type) { |
||||
auto hotkeyName = keymapper.KeyNameForAction(quickSpellActionIndexes[t]); |
||||
PrintSBookHotkey(out, spellListItem.location, hotkeyName); |
||||
strcpy(tempstr, fmt::format(_("Spell Hotkey {:s}"), hotkeyName.c_str()).c_str()); |
||||
AddPanelString(tempstr); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
std::vector<SpellListItem> GetSpellListItems() |
||||
{ |
||||
std::vector<SpellListItem> spellListItems; |
||||
|
||||
uint64_t mask; |
||||
|
||||
int x = PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS; |
||||
int y = PANEL_Y - 17; |
||||
|
||||
for (int i = RSPLTYPE_SKILL; i < RSPLTYPE_INVALID; i++) { |
||||
auto &myPlayer = Players[MyPlayerId]; |
||||
switch ((spell_type)i) { |
||||
case RSPLTYPE_SKILL: |
||||
mask = myPlayer._pAblSpells; |
||||
break; |
||||
case RSPLTYPE_SPELL: |
||||
mask = myPlayer._pMemSpells; |
||||
break; |
||||
case RSPLTYPE_SCROLL: |
||||
mask = myPlayer._pScrlSpells; |
||||
break; |
||||
case RSPLTYPE_CHARGES: |
||||
mask = myPlayer._pISpells; |
||||
break; |
||||
case RSPLTYPE_INVALID: |
||||
break; |
||||
} |
||||
int8_t j = SPL_FIREBOLT; |
||||
for (uint64_t spl = 1; j < MAX_SPELLS; spl <<= 1, j++) { |
||||
if ((mask & spl) == 0) |
||||
continue; |
||||
int lx = x; |
||||
int ly = y - SPLICONLENGTH; |
||||
bool isSelected = (MousePosition.x >= lx && MousePosition.x < lx + SPLICONLENGTH && MousePosition.y >= ly && MousePosition.y < ly + SPLICONLENGTH); |
||||
spellListItems.emplace_back(SpellListItem { { x, y }, (spell_type)i, (spell_id)j, isSelected }); |
||||
x -= SPLICONLENGTH; |
||||
if (x == PANEL_X + 12 - SPLICONLENGTH) { |
||||
x = PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS; |
||||
y -= SPLICONLENGTH; |
||||
} |
||||
} |
||||
if (mask != 0 && x != PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS) |
||||
x -= SPLICONLENGTH; |
||||
if (x == PANEL_X + 12 - SPLICONLENGTH) { |
||||
x = PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS; |
||||
y -= SPLICONLENGTH; |
||||
} |
||||
} |
||||
|
||||
return spellListItems; |
||||
} |
||||
|
||||
void SetSpell() |
||||
{ |
||||
spell_id pSpell; |
||||
spell_type pSplType; |
||||
|
||||
spselflag = false; |
||||
if (!GetSpellListSelection(pSpell, pSplType)) { |
||||
return; |
||||
} |
||||
|
||||
ClearPanel(); |
||||
|
||||
auto &myPlayer = Players[MyPlayerId]; |
||||
myPlayer._pRSpell = pSpell; |
||||
myPlayer._pRSplType = pSplType; |
||||
|
||||
force_redraw = 255; |
||||
} |
||||
|
||||
void SetSpeedSpell(int slot) |
||||
{ |
||||
spell_id pSpell; |
||||
spell_type pSplType; |
||||
|
||||
if (!GetSpellListSelection(pSpell, pSplType)) { |
||||
return; |
||||
} |
||||
auto &myPlayer = Players[MyPlayerId]; |
||||
for (int i = 0; i < 4; ++i) { |
||||
if (myPlayer._pSplHotKey[i] == pSpell && myPlayer._pSplTHotKey[i] == pSplType) |
||||
myPlayer._pSplHotKey[i] = SPL_INVALID; |
||||
} |
||||
myPlayer._pSplHotKey[slot] = pSpell; |
||||
myPlayer._pSplTHotKey[slot] = pSplType; |
||||
} |
||||
|
||||
void ToggleSpell(int slot) |
||||
{ |
||||
uint64_t spells; |
||||
|
||||
auto &myPlayer = Players[MyPlayerId]; |
||||
|
||||
if (myPlayer._pSplHotKey[slot] == SPL_INVALID) { |
||||
return; |
||||
} |
||||
|
||||
switch (myPlayer._pSplTHotKey[slot]) { |
||||
case RSPLTYPE_SKILL: |
||||
spells = myPlayer._pAblSpells; |
||||
break; |
||||
case RSPLTYPE_SPELL: |
||||
spells = myPlayer._pMemSpells; |
||||
break; |
||||
case RSPLTYPE_SCROLL: |
||||
spells = myPlayer._pScrlSpells; |
||||
break; |
||||
case RSPLTYPE_CHARGES: |
||||
spells = myPlayer._pISpells; |
||||
break; |
||||
case RSPLTYPE_INVALID: |
||||
return; |
||||
} |
||||
|
||||
if ((spells & GetSpellBitmask(myPlayer._pSplHotKey[slot])) != 0) { |
||||
myPlayer._pRSpell = myPlayer._pSplHotKey[slot]; |
||||
myPlayer._pRSplType = myPlayer._pSplTHotKey[slot]; |
||||
force_redraw = 255; |
||||
} |
||||
} |
||||
|
||||
void DoSpeedBook() |
||||
{ |
||||
spselflag = true; |
||||
int xo = PANEL_X + 12 + SPLICONLENGTH * 10; |
||||
int yo = PANEL_Y - 17; |
||||
int x = xo + SPLICONLENGTH / 2; |
||||
int y = yo - SPLICONLENGTH / 2; |
||||
|
||||
auto &myPlayer = Players[MyPlayerId]; |
||||
|
||||
if (myPlayer._pRSpell != SPL_INVALID) { |
||||
for (int i = RSPLTYPE_SKILL; i <= RSPLTYPE_CHARGES; i++) { |
||||
uint64_t spells; |
||||
switch (i) { |
||||
case RSPLTYPE_SKILL: |
||||
spells = myPlayer._pAblSpells; |
||||
break; |
||||
case RSPLTYPE_SPELL: |
||||
spells = myPlayer._pMemSpells; |
||||
break; |
||||
case RSPLTYPE_SCROLL: |
||||
spells = myPlayer._pScrlSpells; |
||||
break; |
||||
case RSPLTYPE_CHARGES: |
||||
spells = myPlayer._pISpells; |
||||
break; |
||||
} |
||||
uint64_t spell = 1; |
||||
for (int j = 1; j < MAX_SPELLS; j++) { |
||||
if ((spell & spells) != 0) { |
||||
if (j == myPlayer._pRSpell && i == myPlayer._pRSplType) { |
||||
x = xo + SPLICONLENGTH / 2; |
||||
y = yo - SPLICONLENGTH / 2; |
||||
} |
||||
xo -= SPLICONLENGTH; |
||||
if (xo == PANEL_X + 12 - SPLICONLENGTH) { |
||||
xo = PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS; |
||||
yo -= SPLICONLENGTH; |
||||
} |
||||
} |
||||
spell <<= 1ULL; |
||||
} |
||||
if (spells != 0 && xo != PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS) |
||||
xo -= SPLICONLENGTH; |
||||
if (xo == PANEL_X + 12 - SPLICONLENGTH) { |
||||
xo = PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS; |
||||
yo -= SPLICONLENGTH; |
||||
} |
||||
} |
||||
} |
||||
|
||||
SetCursorPos({ x, y }); |
||||
} |
||||
|
||||
} // namespace devilution
|
||||
@ -0,0 +1,26 @@
|
||||
#pragma once |
||||
|
||||
#include <vector> |
||||
|
||||
#include "engine/point.hpp" |
||||
#include "engine/surface.hpp" |
||||
#include "spelldat.h" |
||||
|
||||
namespace devilution { |
||||
|
||||
struct SpellListItem { |
||||
Point location; |
||||
spell_type type; |
||||
spell_id id; |
||||
bool isSelected; |
||||
}; |
||||
|
||||
void DrawSpell(const Surface &out); |
||||
void DrawSpellList(const Surface &out); |
||||
std::vector<SpellListItem> GetSpellListItems(); |
||||
void SetSpell(); |
||||
void SetSpeedSpell(int slot); |
||||
void ToggleSpell(int slot); |
||||
void DoSpeedBook(); |
||||
|
||||
} // namespace devilution
|
||||
@ -0,0 +1,212 @@
|
||||
#include "panels/spell_book.hpp" |
||||
|
||||
#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 "player.h" |
||||
#include "spelldat.h" |
||||
#include "utils/language.h" |
||||
#include "utils/stdcompat/optional.hpp" |
||||
|
||||
#define SPLICONLAST (gbIsHellfire ? 52 : 43) |
||||
|
||||
namespace devilution { |
||||
namespace { |
||||
|
||||
std::optional<CelSprite> pSBkBtnCel; |
||||
std::optional<CelSprite> pSBkIconCels; |
||||
std::optional<CelSprite> 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) |
||||
{ |
||||
auto &player = Players[MyPlayerId]; |
||||
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 && currlevel == 0 && 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 int SBkBtnHellfireWidths[] = { 0, 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 = Players[MyPlayerId]; |
||||
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, 1); |
||||
if (gbIsHellfire && sbooktab < 5) { |
||||
CelDrawTo(out, GetPanelPosition(UiPanels::Spell, { 61 * sbooktab + 7, 348 }), *pSBkBtnCel, sbooktab + 1); |
||||
} 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 + 1); |
||||
} |
||||
auto &player = Players[MyPlayerId]; |
||||
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; |
||||
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 constrains, keep short please.*/ "Heals: {:d} - {:d}"), min, max), UiFlags::AlignRight); |
||||
} else { |
||||
PrintSBookStr(out, line1, fmt::format(_(/* TRANSLATORS: UI constrains, keep short please.*/ "Damage: {:d} - {:d}"), min, max), UiFlags::AlignRight); |
||||
} |
||||
} |
||||
} else { |
||||
PrintSBookStr(out, line1, _(/* TRANSLATORS: UI constrains, keep short please.*/ "Dmg: 1/3 target hp"), UiFlags::AlignRight); |
||||
} |
||||
PrintSBookStr(out, line1, fmt::format(pgettext(/* TRANSLATORS: UI constrains, keep short please.*/ "spellbook", "Mana: {:d}"), mana)); |
||||
int lvl = std::max(player._pSplLvl[sn] + player._pISplLvlAdd, 0); |
||||
if (lvl == 0) { |
||||
PrintSBookStr(out, line0, _("Level 0 - Unusable"), UiFlags::AlignRight); |
||||
} else { |
||||
PrintSBookStr(out, line0, fmt::format(pgettext(/* TRANSLATORS: UI constrains, keep short please.*/ "spellbook", "Level {:d}"), lvl), UiFlags::AlignRight); |
||||
} |
||||
} 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]; |
||||
auto &player = Players[MyPlayerId]; |
||||
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
|
||||
@ -0,0 +1,152 @@
|
||||
#include "panels/spell_icons.hpp" |
||||
|
||||
#include "engine/load_cel.hpp" |
||||
#include "engine/render/cel_render.hpp" |
||||
#include "init.h" |
||||
#include "palette.h" |
||||
#include "utils/stdcompat/optional.hpp" |
||||
|
||||
namespace devilution { |
||||
|
||||
namespace { |
||||
std::optional<CelSprite> pSpellCels; |
||||
uint8_t SplTransTbl[256]; |
||||
} // namespace
|
||||
|
||||
const char SpellITbl[] = { |
||||
27, |
||||
1, |
||||
2, |
||||
3, |
||||
4, |
||||
5, |
||||
6, |
||||
7, |
||||
8, |
||||
9, |
||||
28, |
||||
13, |
||||
12, |
||||
18, |
||||
16, |
||||
14, |
||||
18, |
||||
19, |
||||
11, |
||||
20, |
||||
15, |
||||
21, |
||||
23, |
||||
24, |
||||
25, |
||||
22, |
||||
26, |
||||
29, |
||||
37, |
||||
38, |
||||
39, |
||||
42, |
||||
41, |
||||
40, |
||||
10, |
||||
36, |
||||
30, |
||||
51, |
||||
51, |
||||
50, |
||||
46, |
||||
47, |
||||
43, |
||||
45, |
||||
48, |
||||
49, |
||||
44, |
||||
35, |
||||
35, |
||||
35, |
||||
35, |
||||
35, |
||||
}; |
||||
|
||||
void LoadSpellIcons() |
||||
{ |
||||
if (!gbIsHellfire) |
||||
pSpellCels = LoadCel("CtrlPan\\SpelIcon.CEL", SPLICONLENGTH); |
||||
else |
||||
pSpellCels = LoadCel("Data\\SpelIcon.CEL", SPLICONLENGTH); |
||||
SetSpellTrans(RSPLTYPE_SKILL); |
||||
} |
||||
|
||||
void FreeSpellIcons() |
||||
{ |
||||
pSpellCels = std::nullopt; |
||||
} |
||||
|
||||
void DrawSpellCel(const Surface &out, Point position, int nCel) |
||||
{ |
||||
DrawSpellCel(out, position, *pSpellCels, nCel); |
||||
} |
||||
|
||||
void DrawSpellCel(const Surface &out, Point position, const CelSprite &sprite, int nCel) |
||||
{ |
||||
CelDrawLightTo(out, position, sprite, nCel, SplTransTbl); |
||||
} |
||||
|
||||
void SetSpellTrans(spell_type t) |
||||
{ |
||||
if (t == RSPLTYPE_SKILL) { |
||||
for (int i = 0; i < 128; i++) |
||||
SplTransTbl[i] = i; |
||||
} |
||||
for (int i = 128; i < 256; i++) |
||||
SplTransTbl[i] = i; |
||||
SplTransTbl[255] = 0; |
||||
|
||||
switch (t) { |
||||
case RSPLTYPE_SPELL: |
||||
SplTransTbl[PAL8_YELLOW] = PAL16_BLUE + 1; |
||||
SplTransTbl[PAL8_YELLOW + 1] = PAL16_BLUE + 3; |
||||
SplTransTbl[PAL8_YELLOW + 2] = PAL16_BLUE + 5; |
||||
for (int i = PAL16_BLUE; i < PAL16_BLUE + 16; i++) { |
||||
SplTransTbl[PAL16_BEIGE - PAL16_BLUE + i] = i; |
||||
SplTransTbl[PAL16_YELLOW - PAL16_BLUE + i] = i; |
||||
SplTransTbl[PAL16_ORANGE - PAL16_BLUE + i] = i; |
||||
} |
||||
break; |
||||
case RSPLTYPE_SCROLL: |
||||
SplTransTbl[PAL8_YELLOW] = PAL16_BEIGE + 1; |
||||
SplTransTbl[PAL8_YELLOW + 1] = PAL16_BEIGE + 3; |
||||
SplTransTbl[PAL8_YELLOW + 2] = PAL16_BEIGE + 5; |
||||
for (int i = PAL16_BEIGE; i < PAL16_BEIGE + 16; i++) { |
||||
SplTransTbl[PAL16_YELLOW - PAL16_BEIGE + i] = i; |
||||
SplTransTbl[PAL16_ORANGE - PAL16_BEIGE + i] = i; |
||||
} |
||||
break; |
||||
case RSPLTYPE_CHARGES: |
||||
SplTransTbl[PAL8_YELLOW] = PAL16_ORANGE + 1; |
||||
SplTransTbl[PAL8_YELLOW + 1] = PAL16_ORANGE + 3; |
||||
SplTransTbl[PAL8_YELLOW + 2] = PAL16_ORANGE + 5; |
||||
for (int i = PAL16_ORANGE; i < PAL16_ORANGE + 16; i++) { |
||||
SplTransTbl[PAL16_BEIGE - PAL16_ORANGE + i] = i; |
||||
SplTransTbl[PAL16_YELLOW - PAL16_ORANGE + i] = i; |
||||
} |
||||
break; |
||||
case RSPLTYPE_INVALID: |
||||
SplTransTbl[PAL8_YELLOW] = PAL16_GRAY + 1; |
||||
SplTransTbl[PAL8_YELLOW + 1] = PAL16_GRAY + 3; |
||||
SplTransTbl[PAL8_YELLOW + 2] = PAL16_GRAY + 5; |
||||
for (int i = PAL16_GRAY; i < PAL16_GRAY + 15; i++) { |
||||
SplTransTbl[PAL16_BEIGE - PAL16_GRAY + i] = i; |
||||
SplTransTbl[PAL16_YELLOW - PAL16_GRAY + i] = i; |
||||
SplTransTbl[PAL16_ORANGE - PAL16_GRAY + i] = i; |
||||
} |
||||
SplTransTbl[PAL16_BEIGE + 15] = 0; |
||||
SplTransTbl[PAL16_YELLOW + 15] = 0; |
||||
SplTransTbl[PAL16_ORANGE + 15] = 0; |
||||
break; |
||||
case RSPLTYPE_SKILL: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
} // namespace devilution
|
||||
@ -0,0 +1,372 @@
|
||||
#include "panels/spell_list.hpp" |
||||
|
||||
#include "control.h" |
||||
#include "controls/keymapper.hpp" |
||||
#include "engine.h" |
||||
#include "engine/render/text_render.hpp" |
||||
#include "inv_iterators.hpp" |
||||
#include "palette.h" |
||||
#include "panels/spell_icons.hpp" |
||||
#include "player.h" |
||||
#include "spells.h" |
||||
#include "utils/language.h" |
||||
|
||||
#define SPLROWICONLS 10 |
||||
|
||||
namespace devilution { |
||||
|
||||
extern std::array<Keymapper::ActionIndex, 4> quickSpellActionIndexes; |
||||
|
||||
namespace { |
||||
|
||||
void PrintSBookSpellType(const Surface &out, Point position, const std::string &text, uint8_t rectColorIndex) |
||||
{ |
||||
Point rect { position }; |
||||
rect += Displacement { 0, -SPLICONLENGTH + 1 }; |
||||
|
||||
// Top
|
||||
DrawHorizontalLine(out, rect, SPLICONLENGTH, rectColorIndex); |
||||
DrawHorizontalLine(out, rect + Displacement { 0, 1 }, SPLICONLENGTH, rectColorIndex); |
||||
|
||||
// Bottom
|
||||
DrawHorizontalLine(out, rect + Displacement { 0, SPLICONLENGTH - 2 }, SPLICONLENGTH, rectColorIndex); |
||||
DrawHorizontalLine(out, rect + Displacement { 0, SPLICONLENGTH - 1 }, SPLICONLENGTH, rectColorIndex); |
||||
|
||||
// Left Side
|
||||
DrawVerticalLine(out, rect, SPLICONLENGTH, rectColorIndex); |
||||
DrawVerticalLine(out, rect + Displacement { 1, 0 }, SPLICONLENGTH, rectColorIndex); |
||||
|
||||
// Right Side
|
||||
DrawVerticalLine(out, rect + Displacement { SPLICONLENGTH - 2, 0 }, SPLICONLENGTH, rectColorIndex); |
||||
DrawVerticalLine(out, rect + Displacement { SPLICONLENGTH - 1, 0 }, SPLICONLENGTH, rectColorIndex); |
||||
|
||||
// Align the spell type text with bottom of spell icon
|
||||
position += Displacement { SPLICONLENGTH / 2 - GetLineWidth(text.c_str()) / 2, (IsSmallFontTall() ? -19 : -15) }; |
||||
|
||||
// Draw a drop shadow below and to the left of the text
|
||||
DrawString(out, text, position + Displacement { -1, 1 }, UiFlags::ColorBlack); |
||||
DrawString(out, text, position + Displacement { -1, -1 }, UiFlags::ColorBlack); |
||||
DrawString(out, text, position + Displacement { 1, -1 }, UiFlags::ColorBlack); |
||||
// Then draw the text over the top
|
||||
DrawString(out, text, position, UiFlags::ColorWhite); |
||||
} |
||||
|
||||
void PrintSBookHotkey(const Surface &out, Point position, const std::string &text) |
||||
{ |
||||
// Align the hot key text with the top-right corner of the spell icon
|
||||
position += Displacement { SPLICONLENGTH - (GetLineWidth(text.c_str()) + 5), 5 - SPLICONLENGTH }; |
||||
|
||||
// Draw a drop shadow below and to the left of the text
|
||||
DrawString(out, text, position + Displacement { -1, 1 }, UiFlags::ColorBlack); |
||||
// Then draw the text over the top
|
||||
DrawString(out, text, position, UiFlags::ColorWhite); |
||||
} |
||||
|
||||
bool GetSpellListSelection(spell_id &pSpell, spell_type &pSplType) |
||||
{ |
||||
pSpell = spell_id::SPL_INVALID; |
||||
pSplType = spell_type::RSPLTYPE_INVALID; |
||||
auto &myPlayer = Players[MyPlayerId]; |
||||
|
||||
for (auto &spellListItem : GetSpellListItems()) { |
||||
if (spellListItem.isSelected) { |
||||
pSpell = spellListItem.id; |
||||
pSplType = spellListItem.type; |
||||
if (myPlayer._pClass == HeroClass::Monk && spellListItem.id == SPL_SEARCH) |
||||
pSplType = RSPLTYPE_SKILL; |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
void DrawSpell(const Surface &out) |
||||
{ |
||||
auto &myPlayer = Players[MyPlayerId]; |
||||
spell_id spl = myPlayer._pRSpell; |
||||
spell_type st = myPlayer._pRSplType; |
||||
|
||||
// BUGFIX: Move the next line into the if statement to avoid OOB (SPL_INVALID is -1) (fixed)
|
||||
if (st == RSPLTYPE_SPELL && spl != SPL_INVALID) { |
||||
int tlvl = myPlayer._pISplLvlAdd + myPlayer._pSplLvl[spl]; |
||||
if (CheckSpell(MyPlayerId, spl, st, true) != SpellCheckResult::Success) |
||||
st = RSPLTYPE_INVALID; |
||||
if (tlvl <= 0) |
||||
st = RSPLTYPE_INVALID; |
||||
} |
||||
if (currlevel == 0 && st != RSPLTYPE_INVALID && !spelldata[spl].sTownSpell) |
||||
st = RSPLTYPE_INVALID; |
||||
SetSpellTrans(st); |
||||
const int nCel = (spl != SPL_INVALID) ? SpellITbl[spl] : 27; |
||||
const Point position { PANEL_X + 565, PANEL_Y + 119 }; |
||||
DrawSpellCel(out, position, nCel); |
||||
} |
||||
|
||||
void DrawSpellList(const Surface &out) |
||||
{ |
||||
infostr[0] = '\0'; |
||||
ClearPanel(); |
||||
|
||||
auto &myPlayer = Players[MyPlayerId]; |
||||
|
||||
for (auto &spellListItem : GetSpellListItems()) { |
||||
const spell_id spellId = spellListItem.id; |
||||
spell_type transType = spellListItem.type; |
||||
int spellLevel = 0; |
||||
const SpellData &spellDataItem = spelldata[static_cast<size_t>(spellListItem.id)]; |
||||
if (currlevel == 0 && !spellDataItem.sTownSpell) { |
||||
transType = RSPLTYPE_INVALID; |
||||
} |
||||
if (spellListItem.type == RSPLTYPE_SPELL) { |
||||
spellLevel = std::max(myPlayer._pISplLvlAdd + myPlayer._pSplLvl[spellListItem.id], 0); |
||||
if (spellLevel == 0) |
||||
transType = RSPLTYPE_INVALID; |
||||
} |
||||
|
||||
SetSpellTrans(transType); |
||||
DrawSpellCel(out, spellListItem.location, SpellITbl[static_cast<size_t>(spellId)]); |
||||
|
||||
if (!spellListItem.isSelected) |
||||
continue; |
||||
|
||||
uint8_t spellColor = PAL16_GRAY + 5; |
||||
|
||||
switch (spellListItem.type) { |
||||
case RSPLTYPE_SKILL: |
||||
spellColor = PAL16_YELLOW - 46; |
||||
PrintSBookSpellType(out, spellListItem.location, _("Skill"), spellColor); |
||||
strcpy(infostr, fmt::format(_("{:s} Skill"), pgettext("spell", spellDataItem.sSkillText)).c_str()); |
||||
break; |
||||
case RSPLTYPE_SPELL: |
||||
if (myPlayer.plrlevel != 0) { |
||||
spellColor = PAL16_BLUE + 5; |
||||
} |
||||
PrintSBookSpellType(out, spellListItem.location, _("Spell"), spellColor); |
||||
strcpy(infostr, fmt::format(_("{:s} Spell"), pgettext("spell", spellDataItem.sNameText)).c_str()); |
||||
if (spellId == SPL_HBOLT) { |
||||
strcpy(tempstr, _("Damages undead only")); |
||||
AddPanelString(tempstr); |
||||
} |
||||
if (spellLevel == 0) |
||||
strcpy(tempstr, _("Spell Level 0 - Unusable")); |
||||
else |
||||
strcpy(tempstr, fmt::format(_("Spell Level {:d}"), spellLevel).c_str()); |
||||
AddPanelString(tempstr); |
||||
break; |
||||
case RSPLTYPE_SCROLL: { |
||||
if (myPlayer.plrlevel != 0) { |
||||
spellColor = PAL16_RED - 59; |
||||
} |
||||
PrintSBookSpellType(out, spellListItem.location, _("Scroll"), spellColor); |
||||
strcpy(infostr, fmt::format(_("Scroll of {:s}"), pgettext("spell", spellDataItem.sNameText)).c_str()); |
||||
const InventoryAndBeltPlayerItemsRange items { myPlayer }; |
||||
const int scrollCount = std::count_if(items.begin(), items.end(), [spellId](const Item &item) { |
||||
return item.IsScrollOf(spellId); |
||||
}); |
||||
strcpy(tempstr, fmt::format(ngettext("{:d} Scroll", "{:d} Scrolls", scrollCount), scrollCount).c_str()); |
||||
AddPanelString(tempstr); |
||||
} break; |
||||
case RSPLTYPE_CHARGES: { |
||||
if (myPlayer.plrlevel != 0) { |
||||
spellColor = PAL16_ORANGE + 5; |
||||
} |
||||
PrintSBookSpellType(out, spellListItem.location, _("Staff"), spellColor); |
||||
strcpy(infostr, fmt::format(_("Staff of {:s}"), pgettext("spell", spellDataItem.sNameText)).c_str()); |
||||
int charges = myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges; |
||||
strcpy(tempstr, fmt::format(ngettext("{:d} Charge", "{:d} Charges", charges), charges).c_str()); |
||||
AddPanelString(tempstr); |
||||
} break; |
||||
case RSPLTYPE_INVALID: |
||||
break; |
||||
} |
||||
for (int t = 0; t < 4; t++) { |
||||
if (myPlayer._pSplHotKey[t] == spellId && myPlayer._pSplTHotKey[t] == spellListItem.type) { |
||||
auto hotkeyName = keymapper.KeyNameForAction(quickSpellActionIndexes[t]); |
||||
PrintSBookHotkey(out, spellListItem.location, hotkeyName); |
||||
strcpy(tempstr, fmt::format(_("Spell Hotkey {:s}"), hotkeyName.c_str()).c_str()); |
||||
AddPanelString(tempstr); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
std::vector<SpellListItem> GetSpellListItems() |
||||
{ |
||||
std::vector<SpellListItem> spellListItems; |
||||
|
||||
uint64_t mask; |
||||
|
||||
int x = PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS; |
||||
int y = PANEL_Y - 17; |
||||
|
||||
for (int i = RSPLTYPE_SKILL; i < RSPLTYPE_INVALID; i++) { |
||||
auto &myPlayer = Players[MyPlayerId]; |
||||
switch ((spell_type)i) { |
||||
case RSPLTYPE_SKILL: |
||||
mask = myPlayer._pAblSpells; |
||||
break; |
||||
case RSPLTYPE_SPELL: |
||||
mask = myPlayer._pMemSpells; |
||||
break; |
||||
case RSPLTYPE_SCROLL: |
||||
mask = myPlayer._pScrlSpells; |
||||
break; |
||||
case RSPLTYPE_CHARGES: |
||||
mask = myPlayer._pISpells; |
||||
break; |
||||
case RSPLTYPE_INVALID: |
||||
break; |
||||
} |
||||
int8_t j = SPL_FIREBOLT; |
||||
for (uint64_t spl = 1; j < MAX_SPELLS; spl <<= 1, j++) { |
||||
if ((mask & spl) == 0) |
||||
continue; |
||||
int lx = x; |
||||
int ly = y - SPLICONLENGTH; |
||||
bool isSelected = (MousePosition.x >= lx && MousePosition.x < lx + SPLICONLENGTH && MousePosition.y >= ly && MousePosition.y < ly + SPLICONLENGTH); |
||||
spellListItems.emplace_back(SpellListItem { { x, y }, (spell_type)i, (spell_id)j, isSelected }); |
||||
x -= SPLICONLENGTH; |
||||
if (x == PANEL_X + 12 - SPLICONLENGTH) { |
||||
x = PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS; |
||||
y -= SPLICONLENGTH; |
||||
} |
||||
} |
||||
if (mask != 0 && x != PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS) |
||||
x -= SPLICONLENGTH; |
||||
if (x == PANEL_X + 12 - SPLICONLENGTH) { |
||||
x = PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS; |
||||
y -= SPLICONLENGTH; |
||||
} |
||||
} |
||||
|
||||
return spellListItems; |
||||
} |
||||
|
||||
void SetSpell() |
||||
{ |
||||
spell_id pSpell; |
||||
spell_type pSplType; |
||||
|
||||
spselflag = false; |
||||
if (!GetSpellListSelection(pSpell, pSplType)) { |
||||
return; |
||||
} |
||||
|
||||
ClearPanel(); |
||||
|
||||
auto &myPlayer = Players[MyPlayerId]; |
||||
myPlayer._pRSpell = pSpell; |
||||
myPlayer._pRSplType = pSplType; |
||||
|
||||
force_redraw = 255; |
||||
} |
||||
|
||||
void SetSpeedSpell(int slot) |
||||
{ |
||||
spell_id pSpell; |
||||
spell_type pSplType; |
||||
|
||||
if (!GetSpellListSelection(pSpell, pSplType)) { |
||||
return; |
||||
} |
||||
auto &myPlayer = Players[MyPlayerId]; |
||||
for (int i = 0; i < 4; ++i) { |
||||
if (myPlayer._pSplHotKey[i] == pSpell && myPlayer._pSplTHotKey[i] == pSplType) |
||||
myPlayer._pSplHotKey[i] = SPL_INVALID; |
||||
} |
||||
myPlayer._pSplHotKey[slot] = pSpell; |
||||
myPlayer._pSplTHotKey[slot] = pSplType; |
||||
} |
||||
|
||||
void ToggleSpell(int slot) |
||||
{ |
||||
uint64_t spells; |
||||
|
||||
auto &myPlayer = Players[MyPlayerId]; |
||||
|
||||
if (myPlayer._pSplHotKey[slot] == SPL_INVALID) { |
||||
return; |
||||
} |
||||
|
||||
switch (myPlayer._pSplTHotKey[slot]) { |
||||
case RSPLTYPE_SKILL: |
||||
spells = myPlayer._pAblSpells; |
||||
break; |
||||
case RSPLTYPE_SPELL: |
||||
spells = myPlayer._pMemSpells; |
||||
break; |
||||
case RSPLTYPE_SCROLL: |
||||
spells = myPlayer._pScrlSpells; |
||||
break; |
||||
case RSPLTYPE_CHARGES: |
||||
spells = myPlayer._pISpells; |
||||
break; |
||||
case RSPLTYPE_INVALID: |
||||
return; |
||||
} |
||||
|
||||
if ((spells & GetSpellBitmask(myPlayer._pSplHotKey[slot])) != 0) { |
||||
myPlayer._pRSpell = myPlayer._pSplHotKey[slot]; |
||||
myPlayer._pRSplType = myPlayer._pSplTHotKey[slot]; |
||||
force_redraw = 255; |
||||
} |
||||
} |
||||
|
||||
void DoSpeedBook() |
||||
{ |
||||
spselflag = true; |
||||
int xo = PANEL_X + 12 + SPLICONLENGTH * 10; |
||||
int yo = PANEL_Y - 17; |
||||
int x = xo + SPLICONLENGTH / 2; |
||||
int y = yo - SPLICONLENGTH / 2; |
||||
|
||||
auto &myPlayer = Players[MyPlayerId]; |
||||
|
||||
if (myPlayer._pRSpell != SPL_INVALID) { |
||||
for (int i = RSPLTYPE_SKILL; i <= RSPLTYPE_CHARGES; i++) { |
||||
uint64_t spells; |
||||
switch (i) { |
||||
case RSPLTYPE_SKILL: |
||||
spells = myPlayer._pAblSpells; |
||||
break; |
||||
case RSPLTYPE_SPELL: |
||||
spells = myPlayer._pMemSpells; |
||||
break; |
||||
case RSPLTYPE_SCROLL: |
||||
spells = myPlayer._pScrlSpells; |
||||
break; |
||||
case RSPLTYPE_CHARGES: |
||||
spells = myPlayer._pISpells; |
||||
break; |
||||
} |
||||
uint64_t spell = 1; |
||||
for (int j = 1; j < MAX_SPELLS; j++) { |
||||
if ((spell & spells) != 0) { |
||||
if (j == myPlayer._pRSpell && i == myPlayer._pRSplType) { |
||||
x = xo + SPLICONLENGTH / 2; |
||||
y = yo - SPLICONLENGTH / 2; |
||||
} |
||||
xo -= SPLICONLENGTH; |
||||
if (xo == PANEL_X + 12 - SPLICONLENGTH) { |
||||
xo = PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS; |
||||
yo -= SPLICONLENGTH; |
||||
} |
||||
} |
||||
spell <<= 1ULL; |
||||
} |
||||
if (spells != 0 && xo != PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS) |
||||
xo -= SPLICONLENGTH; |
||||
if (xo == PANEL_X + 12 - SPLICONLENGTH) { |
||||
xo = PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS; |
||||
yo -= SPLICONLENGTH; |
||||
} |
||||
} |
||||
} |
||||
|
||||
SetCursorPos({ x, y }); |
||||
} |
||||
|
||||
} // namespace devilution
|
||||
Loading…
Reference in new issue