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.
377 lines
11 KiB
377 lines
11 KiB
#include "panels/spell_list.hpp" |
|
|
|
#include <cstdint> |
|
|
|
#include <fmt/format.h> |
|
|
|
#include "control.h" |
|
#include "controls/plrctrls.h" |
|
#include "engine.h" |
|
#include "engine/backbuffer_state.hpp" |
|
#include "engine/palette.h" |
|
#include "engine/render/text_render.hpp" |
|
#include "inv_iterators.hpp" |
|
#include "options.h" |
|
#include "panels/spell_icons.hpp" |
|
#include "player.h" |
|
#include "spells.h" |
|
#include "utils/language.h" |
|
#include "utils/str_cat.hpp" |
|
#include "utils/utf8.hpp" |
|
|
|
#define SPLROWICONLS 10 |
|
|
|
namespace devilution { |
|
|
|
namespace { |
|
|
|
void PrintSBookSpellType(const Surface &out, Point position, string_view text, uint8_t rectColorIndex) |
|
{ |
|
DrawLargeSpellIconBorder(out, position, rectColorIndex); |
|
|
|
// Align the spell type text with bottom of spell icon |
|
position += Displacement { SPLICONLENGTH / 2 - GetLineWidth(text) / 2, (IsSmallFontTall() ? -19 : -15) }; |
|
|
|
// Then draw the text over the top |
|
DrawString(out, text, position, UiFlags::ColorWhite | UiFlags::Outlined); |
|
} |
|
|
|
void PrintSBookHotkey(const Surface &out, Point position, const string_view text) |
|
{ |
|
// Align the hot key text with the top-right corner of the spell icon |
|
position += Displacement { SPLICONLENGTH - (GetLineWidth(text.data()) + 5), 5 - SPLICONLENGTH }; |
|
|
|
// Then draw the text over the top |
|
DrawString(out, text, position, UiFlags::ColorWhite | UiFlags::Outlined); |
|
} |
|
|
|
bool GetSpellListSelection(SpellID &pSpell, SpellType &pSplType) |
|
{ |
|
pSpell = SpellID::Invalid; |
|
pSplType = SpellType::Invalid; |
|
Player &myPlayer = *MyPlayer; |
|
|
|
for (auto &spellListItem : GetSpellListItems()) { |
|
if (spellListItem.isSelected) { |
|
pSpell = spellListItem.id; |
|
pSplType = spellListItem.type; |
|
if (myPlayer._pClass == HeroClass::Monk && spellListItem.id == SpellID::Search) |
|
pSplType = SpellType::Skill; |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
std::optional<string_view> GetHotkeyName(SpellID spellId, SpellType spellType, bool useShortName = false) |
|
{ |
|
Player &myPlayer = *MyPlayer; |
|
for (size_t t = 0; t < NumHotkeys; t++) { |
|
if (myPlayer._pSplHotKey[t] != spellId || myPlayer._pSplTHotKey[t] != spellType) |
|
continue; |
|
auto quickSpellActionKey = StrCat("QuickSpell", t + 1); |
|
if (ControlMode == ControlTypes::Gamepad) |
|
return sgOptions.Padmapper.InputNameForAction(quickSpellActionKey, useShortName); |
|
return sgOptions.Keymapper.KeyNameForAction(quickSpellActionKey); |
|
} |
|
return {}; |
|
} |
|
|
|
} // namespace |
|
|
|
void DrawSpell(const Surface &out) |
|
{ |
|
Player &myPlayer = *MyPlayer; |
|
SpellID spl = myPlayer._pRSpell; |
|
SpellType st = myPlayer._pRSplType; |
|
|
|
if (!IsValidSpell(spl)) { |
|
st = SpellType::Invalid; |
|
spl = SpellID::Null; |
|
} |
|
|
|
if (st == SpellType::Spell) { |
|
int tlvl = myPlayer.GetSpellLevel(spl); |
|
if (CheckSpell(*MyPlayer, spl, st, true) != SpellCheckResult::Success) |
|
st = SpellType::Invalid; |
|
if (tlvl <= 0) |
|
st = SpellType::Invalid; |
|
} |
|
|
|
if (leveltype == DTYPE_TOWN && st != SpellType::Invalid && !GetSpellData(spl).isAllowedInTown()) |
|
st = SpellType::Invalid; |
|
|
|
SetSpellTrans(st); |
|
const Point position = GetMainPanel().position + Displacement { 565, 119 }; |
|
DrawLargeSpellIcon(out, position, spl); |
|
|
|
std::optional<string_view> hotkeyName = GetHotkeyName(spl, myPlayer._pRSplType, true); |
|
if (hotkeyName) |
|
PrintSBookHotkey(out, position, *hotkeyName); |
|
} |
|
|
|
void DrawSpellList(const Surface &out) |
|
{ |
|
InfoString = {}; |
|
|
|
Player &myPlayer = *MyPlayer; |
|
|
|
for (auto &spellListItem : GetSpellListItems()) { |
|
const SpellID spellId = spellListItem.id; |
|
SpellType transType = spellListItem.type; |
|
int spellLevel = 0; |
|
const SpellData &spellDataItem = GetSpellData(spellListItem.id); |
|
if (leveltype == DTYPE_TOWN && !spellDataItem.isAllowedInTown()) { |
|
transType = SpellType::Invalid; |
|
} |
|
if (spellListItem.type == SpellType::Spell) { |
|
spellLevel = myPlayer.GetSpellLevel(spellListItem.id); |
|
if (spellLevel == 0) |
|
transType = SpellType::Invalid; |
|
} |
|
|
|
SetSpellTrans(transType); |
|
DrawLargeSpellIcon(out, spellListItem.location, spellId); |
|
|
|
std::optional<string_view> shortHotkeyName = GetHotkeyName(spellId, spellListItem.type, true); |
|
|
|
if (shortHotkeyName) |
|
PrintSBookHotkey(out, spellListItem.location, *shortHotkeyName); |
|
|
|
if (!spellListItem.isSelected) |
|
continue; |
|
|
|
uint8_t spellColor = PAL16_GRAY + 5; |
|
|
|
switch (spellListItem.type) { |
|
case SpellType::Skill: |
|
spellColor = PAL16_YELLOW - 46; |
|
PrintSBookSpellType(out, spellListItem.location, _("Skill"), spellColor); |
|
InfoString = fmt::format(fmt::runtime(_("{:s} Skill")), pgettext("spell", spellDataItem.sNameText)); |
|
break; |
|
case SpellType::Spell: |
|
if (!myPlayer.isOnLevel(0)) { |
|
spellColor = PAL16_BLUE + 5; |
|
} |
|
PrintSBookSpellType(out, spellListItem.location, _("Spell"), spellColor); |
|
InfoString = fmt::format(fmt::runtime(_("{:s} Spell")), pgettext("spell", spellDataItem.sNameText)); |
|
if (spellId == SpellID::HolyBolt) { |
|
AddPanelString(_("Damages undead only")); |
|
} |
|
if (spellLevel == 0) |
|
AddPanelString(_("Spell Level 0 - Unusable")); |
|
else |
|
AddPanelString(fmt::format(fmt::runtime(_("Spell Level {:d}")), spellLevel)); |
|
break; |
|
case SpellType::Scroll: { |
|
if (!myPlayer.isOnLevel(0)) { |
|
spellColor = PAL16_RED - 59; |
|
} |
|
PrintSBookSpellType(out, spellListItem.location, _("Scroll"), spellColor); |
|
InfoString = fmt::format(fmt::runtime(_("Scroll of {:s}")), pgettext("spell", spellDataItem.sNameText)); |
|
const InventoryAndBeltPlayerItemsRange items { myPlayer }; |
|
const int scrollCount = std::count_if(items.begin(), items.end(), [spellId](const Item &item) { |
|
return item.isScrollOf(spellId); |
|
}); |
|
AddPanelString(fmt::format(fmt::runtime(ngettext("{:d} Scroll", "{:d} Scrolls", scrollCount)), scrollCount)); |
|
} break; |
|
case SpellType::Charges: { |
|
if (!myPlayer.isOnLevel(0)) { |
|
spellColor = PAL16_ORANGE + 5; |
|
} |
|
PrintSBookSpellType(out, spellListItem.location, _("Staff"), spellColor); |
|
InfoString = fmt::format(fmt::runtime(_("Staff of {:s}")), pgettext("spell", spellDataItem.sNameText)); |
|
int charges = myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges; |
|
AddPanelString(fmt::format(fmt::runtime(ngettext("{:d} Charge", "{:d} Charges", charges)), charges)); |
|
} break; |
|
case SpellType::Invalid: |
|
break; |
|
} |
|
std::optional<string_view> fullHotkeyName = GetHotkeyName(spellId, spellListItem.type); |
|
if (fullHotkeyName) { |
|
AddPanelString(fmt::format(fmt::runtime(_("Spell Hotkey {:s}")), *fullHotkeyName)); |
|
} |
|
} |
|
} |
|
|
|
std::vector<SpellListItem> GetSpellListItems() |
|
{ |
|
std::vector<SpellListItem> spellListItems; |
|
|
|
uint64_t mask; |
|
const Point mainPanelPosition = GetMainPanel().position; |
|
|
|
int x = mainPanelPosition.x + 12 + SPLICONLENGTH * SPLROWICONLS; |
|
int y = mainPanelPosition.y - 17; |
|
|
|
for (auto i : enum_values<SpellType>()) { |
|
Player &myPlayer = *MyPlayer; |
|
switch (static_cast<SpellType>(i)) { |
|
case SpellType::Skill: |
|
mask = myPlayer._pAblSpells; |
|
break; |
|
case SpellType::Spell: |
|
mask = myPlayer._pMemSpells; |
|
break; |
|
case SpellType::Scroll: |
|
mask = myPlayer._pScrlSpells; |
|
break; |
|
case SpellType::Charges: |
|
mask = myPlayer._pISpells; |
|
break; |
|
default: |
|
continue; |
|
} |
|
int8_t j = static_cast<int8_t>(SpellID::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 }, static_cast<SpellType>(i), static_cast<SpellID>(j), isSelected }); |
|
x -= SPLICONLENGTH; |
|
if (x == mainPanelPosition.x + 12 - SPLICONLENGTH) { |
|
x = mainPanelPosition.x + 12 + SPLICONLENGTH * SPLROWICONLS; |
|
y -= SPLICONLENGTH; |
|
} |
|
} |
|
if (mask != 0 && x != mainPanelPosition.x + 12 + SPLICONLENGTH * SPLROWICONLS) |
|
x -= SPLICONLENGTH; |
|
if (x == mainPanelPosition.x + 12 - SPLICONLENGTH) { |
|
x = mainPanelPosition.x + 12 + SPLICONLENGTH * SPLROWICONLS; |
|
y -= SPLICONLENGTH; |
|
} |
|
} |
|
|
|
return spellListItems; |
|
} |
|
|
|
void SetSpell() |
|
{ |
|
SpellID pSpell; |
|
SpellType pSplType; |
|
|
|
spselflag = false; |
|
if (!GetSpellListSelection(pSpell, pSplType)) { |
|
return; |
|
} |
|
|
|
Player &myPlayer = *MyPlayer; |
|
myPlayer._pRSpell = pSpell; |
|
myPlayer._pRSplType = pSplType; |
|
|
|
RedrawEverything(); |
|
} |
|
|
|
void SetSpeedSpell(size_t slot) |
|
{ |
|
SpellID pSpell; |
|
SpellType pSplType; |
|
|
|
if (!GetSpellListSelection(pSpell, pSplType)) { |
|
return; |
|
} |
|
Player &myPlayer = *MyPlayer; |
|
for (size_t i = 0; i < NumHotkeys; ++i) { |
|
if (myPlayer._pSplHotKey[i] == pSpell && myPlayer._pSplTHotKey[i] == pSplType) |
|
myPlayer._pSplHotKey[i] = SpellID::Invalid; |
|
} |
|
myPlayer._pSplHotKey[slot] = pSpell; |
|
myPlayer._pSplTHotKey[slot] = pSplType; |
|
} |
|
|
|
void ToggleSpell(size_t slot) |
|
{ |
|
uint64_t spells; |
|
|
|
Player &myPlayer = *MyPlayer; |
|
|
|
const SpellID spellId = myPlayer._pSplHotKey[slot]; |
|
if (!IsValidSpell(spellId)) { |
|
return; |
|
} |
|
|
|
switch (myPlayer._pSplTHotKey[slot]) { |
|
case SpellType::Skill: |
|
spells = myPlayer._pAblSpells; |
|
break; |
|
case SpellType::Spell: |
|
spells = myPlayer._pMemSpells; |
|
break; |
|
case SpellType::Scroll: |
|
spells = myPlayer._pScrlSpells; |
|
break; |
|
case SpellType::Charges: |
|
spells = myPlayer._pISpells; |
|
break; |
|
case SpellType::Invalid: |
|
return; |
|
} |
|
|
|
if ((spells & GetSpellBitmask(spellId)) != 0) { |
|
myPlayer._pRSpell = spellId; |
|
myPlayer._pRSplType = myPlayer._pSplTHotKey[slot]; |
|
RedrawEverything(); |
|
} |
|
} |
|
|
|
void DoSpeedBook() |
|
{ |
|
spselflag = true; |
|
const Point mainPanelPosition = GetMainPanel().position; |
|
int xo = mainPanelPosition.x + 12 + SPLICONLENGTH * 10; |
|
int yo = mainPanelPosition.y - 17; |
|
int x = xo + SPLICONLENGTH / 2; |
|
int y = yo - SPLICONLENGTH / 2; |
|
|
|
Player &myPlayer = *MyPlayer; |
|
|
|
if (IsValidSpell(myPlayer._pRSpell)) { |
|
for (auto i : enum_values<SpellType>()) { |
|
uint64_t spells; |
|
switch (static_cast<SpellType>(i)) { |
|
case SpellType::Skill: |
|
spells = myPlayer._pAblSpells; |
|
break; |
|
case SpellType::Spell: |
|
spells = myPlayer._pMemSpells; |
|
break; |
|
case SpellType::Scroll: |
|
spells = myPlayer._pScrlSpells; |
|
break; |
|
case SpellType::Charges: |
|
spells = myPlayer._pISpells; |
|
break; |
|
default: |
|
continue; |
|
} |
|
uint64_t spell = 1; |
|
for (int j = 1; j < MAX_SPELLS; j++) { |
|
if ((spell & spells) != 0) { |
|
if (j == static_cast<int8_t>(myPlayer._pRSpell) && static_cast<SpellType>(i) == myPlayer._pRSplType) { |
|
x = xo + SPLICONLENGTH / 2; |
|
y = yo - SPLICONLENGTH / 2; |
|
} |
|
xo -= SPLICONLENGTH; |
|
if (xo == mainPanelPosition.x + 12 - SPLICONLENGTH) { |
|
xo = mainPanelPosition.x + 12 + SPLICONLENGTH * SPLROWICONLS; |
|
yo -= SPLICONLENGTH; |
|
} |
|
} |
|
spell <<= 1ULL; |
|
} |
|
if (spells != 0 && xo != mainPanelPosition.x + 12 + SPLICONLENGTH * SPLROWICONLS) |
|
xo -= SPLICONLENGTH; |
|
if (xo == mainPanelPosition.x + 12 - SPLICONLENGTH) { |
|
xo = mainPanelPosition.x + 12 + SPLICONLENGTH * SPLROWICONLS; |
|
yo -= SPLICONLENGTH; |
|
} |
|
} |
|
} |
|
|
|
SetCursorPos({ x, y }); |
|
} |
|
|
|
} // namespace devilution
|
|
|