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.
 
 
 
 
 
 

2792 lines
64 KiB

/**
* @file stores.cpp
*
* Implementation of functionality for stores and towner dialogs.
*/
#include "stores.h"
#include <algorithm>
#include <fmt/format.h>
#include "controls/plrctrls.h"
#include "cursor.h"
#include "engine/load_cel.hpp"
#include "engine/random.hpp"
#include "engine/render/cel_render.hpp"
#include "engine/render/text_render.hpp"
#include "init.h"
#include "minitext.h"
#include "options.h"
#include "panels/info_box.hpp"
#include "qol/stash.h"
#include "towners.h"
#include "utils/language.h"
#include "utils/stdcompat/string_view.hpp"
#include "utils/utf8.hpp"
namespace devilution {
talk_id stextflag;
int storenumh;
char storehidx[48];
Item storehold[48];
Item smithitem[SMITH_ITEMS];
int numpremium;
int premiumlevel;
Item premiumitems[SMITH_PREMIUM_ITEMS];
Item healitem[20];
Item witchitem[WITCH_ITEMS];
int boylevel;
Item boyitem;
namespace {
/** The current towner being interacted with */
_talker_id talker;
/** Is the current dialog full size */
bool stextsize;
/** Number of text lines in the current dialog */
int stextsmax;
/** Remember currently selected text line from stext while displaying a dialog */
int stextlhold;
/** Currently selected text line from stext */
int stextsel;
/** Text lines */
STextStruct stext[STORE_LINES];
/** Whether to render the player's gold amount in the top left */
bool RenderGold;
/** Does the current panel have a scrollbar */
bool stextscrl;
/** Remember last scoll position */
int stextvhold;
/** Scoll position */
int stextsval;
/** Next scoll position */
int stextdown;
/** Previous scoll position */
int stextup;
/** Count down for the push state of the scroll up button */
char stextscrlubtn;
/** Count down for the push state of the scroll down button */
char stextscrldbtn;
/** Remember current store while displaying a dialog */
talk_id stextshold;
/** Start of possible gossip dialogs for current store */
_speech_id gossipstart;
/** End of possible gossip dialogs for current store */
_speech_id gossipend;
/** Temporary item used to hold the the item being traided */
Item StoreItem;
/** Maps from towner IDs to NPC names. */
const char *const TownerNames[] = {
N_("Griswold"),
N_("Pepin"),
"",
N_("Ogden"),
N_("Cain"),
N_("Farnham"),
N_("Adria"),
N_("Gillian"),
N_("Wirt"),
};
constexpr int PaddingTop = 32;
// For most languages, line height is always 12.
// This includes blank lines and divider line.
constexpr int SmallLineHeight = 12;
constexpr int SmallTextHeight = 12;
// For larger small fonts (Chinese and Japanese), text lines are
// taller and overflow.
// We space out blank lines a bit more to give space to 3-line store items.
constexpr int LargeLineHeight = SmallLineHeight + 1;
constexpr int LargeTextHeight = 18;
/**
* The line index with the Back / Leave button.
* This is a special button that is always the last line.
*
* For lists with a scrollbar, it is not selectable (mouse-only).
*/
int BackButtonLine()
{
if (IsSmallFontTall()) {
return stextscrl ? 21 : 20;
}
return 22;
}
int LineHeight()
{
return IsSmallFontTall() ? LargeLineHeight : SmallLineHeight;
}
int TextHeight()
{
return IsSmallFontTall() ? LargeTextHeight : SmallTextHeight;
}
void CalculateLineHeights()
{
stext[0].y = 0;
if (IsSmallFontTall()) {
for (int i = 1; i < STORE_LINES; ++i) {
// Space out consecutive text lines, unless they are both selectable (never the case currently).
if (stext[i].IsText() && stext[i - 1].IsText() && !(stext[i]._ssel && stext[i - 1]._ssel)) {
stext[i].y = stext[i - 1].y + LargeTextHeight;
} else {
stext[i].y = i * LargeLineHeight;
}
}
} else {
for (int i = 1; i < STORE_LINES; ++i) {
stext[i].y = i * SmallLineHeight;
}
}
}
void DrawSTextBack(const Surface &out)
{
CelDrawTo(out, { PANEL_X + 320 + 24, 327 + UI_OFFSET_Y }, *pSTextBoxCels, 0);
DrawHalfTransparentRectTo(out, PANEL_X + 347, UI_OFFSET_Y + 28, 265, 297);
}
void DrawSSlider(const Surface &out, int y1, int y2)
{
int yd1 = y1 * 12 + 44 + UI_OFFSET_Y;
int yd2 = y2 * 12 + 44 + UI_OFFSET_Y;
if (stextscrlubtn != -1)
CelDrawTo(out, { PANEL_X + 601, yd1 }, *pSTextSlidCels, 11);
else
CelDrawTo(out, { PANEL_X + 601, yd1 }, *pSTextSlidCels, 9);
if (stextscrldbtn != -1)
CelDrawTo(out, { PANEL_X + 601, yd2 }, *pSTextSlidCels, 10);
else
CelDrawTo(out, { PANEL_X + 601, yd2 }, *pSTextSlidCels, 8);
yd1 += 12;
int yd3 = yd1;
for (; yd3 < yd2; yd3 += 12) {
CelDrawTo(out, { PANEL_X + 601, yd3 }, *pSTextSlidCels, 13);
}
if (stextsel == BackButtonLine())
yd3 = stextlhold;
else
yd3 = stextsel;
if (storenumh > 1)
yd3 = 1000 * (stextsval + ((yd3 - stextup) / 4)) / (storenumh - 1) * (y2 * 12 - y1 * 12 - 24) / 1000;
else
yd3 = 0;
CelDrawTo(out, { PANEL_X + 601, (y1 + 1) * 12 + 44 + UI_OFFSET_Y + yd3 }, *pSTextSlidCels, 12);
}
void AddSLine(int y)
{
stext[y]._sx = 0;
stext[y]._syoff = 0;
stext[y]._sstr[0] = 0;
stext[y]._sline = 1;
}
void AddSTextVal(int y, int val)
{
stext[y]._sval = val;
}
void AddSText(int x, int y, string_view text, UiFlags flags, bool sel)
{
stext[y]._sx = x;
stext[y]._syoff = 0;
CopyUtf8(stext[y]._sstr, text, sizeof(stext[y]._sstr));
stext[y].flags = flags;
stext[y]._sline = 0;
stext[y]._ssel = sel;
}
void AddOptionsBackButton()
{
const int line = BackButtonLine();
AddSText(0, line, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
stext[line]._syoff = IsSmallFontTall() ? 0 : 6;
}
void AddItemListBackButton(bool selectable = false)
{
const int line = BackButtonLine();
string_view text = _("Back");
if (!selectable && IsSmallFontTall()) {
AddSText(0, line, text, UiFlags::ColorWhite | UiFlags::AlignRight, selectable);
} else {
AddSLine(line - 1);
AddSText(0, line, text, UiFlags::ColorWhite | UiFlags::AlignCenter, selectable);
stext[line]._syoff = 6;
}
}
void PrintStoreItem(const Item &item, int l, UiFlags flags)
{
std::string productLine = "";
if (item._iIdentified) {
if (item._iMagical != ITEM_QUALITY_UNIQUE) {
if (item._iPrePower != -1) {
productLine.append(PrintItemPower(item._iPrePower, item));
}
}
if (item._iSufPower != -1) {
if (!productLine.empty())
productLine.append(_(", "));
productLine.append(PrintItemPower(item._iSufPower, item));
}
}
if (item._iMiscId == IMISC_STAFF && item._iMaxCharges != 0) {
if (!productLine.empty())
productLine.append(_(", "));
productLine.append(fmt::format(_("Charges: {:d}/{:d}"), item._iCharges, item._iMaxCharges));
}
if (!productLine.empty()) {
AddSText(40, l, productLine, flags, false);
l++;
productLine = "";
}
if (item._itype != ItemType::Misc) {
if (item._iClass == ICLASS_WEAPON)
productLine = fmt::format(_("Damage: {:d}-{:d} "), item._iMinDam, item._iMaxDam);
else if (item._iClass == ICLASS_ARMOR)
productLine = fmt::format(_("Armor: {:d} "), item._iAC);
if (item._iMaxDur != DUR_INDESTRUCTIBLE && item._iMaxDur != 0)
productLine += fmt::format(_("Dur: {:d}/{:d}, "), item._iDurability, item._iMaxDur);
else
productLine += _("Indestructible, ");
}
int8_t str = item._iMinStr;
uint8_t mag = item._iMinMag;
int8_t dex = item._iMinDex;
if (str == 0 && mag == 0 && dex == 0) {
productLine.append(_("No required attributes"));
} else {
productLine.append(_("Required:"));
if (str != 0)
productLine.append(fmt::format(_(" {:d} Str"), str));
if (mag != 0)
productLine.append(fmt::format(_(" {:d} Mag"), mag));
if (dex != 0)
productLine.append(fmt::format(_(" {:d} Dex"), dex));
}
AddSText(40, l++, productLine, flags, false);
}
bool StoreAutoPlace(Item &item, bool persistItem)
{
auto &myPlayer = Players[MyPlayerId];
if (AutoEquipEnabled(myPlayer, item) && AutoEquip(MyPlayerId, item, persistItem)) {
return true;
}
if (AutoPlaceItemInBelt(myPlayer, item, persistItem)) {
return true;
}
return AutoPlaceItemInInventory(myPlayer, item, persistItem);
}
void StartSmith()
{
stextsize = false;
stextscrl = false;
AddSText(0, 1, _("Welcome to the"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 3, _("Blacksmith's shop"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 7, _("Would you like to:"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 10, _("Talk to Griswold"), UiFlags::ColorBlue | UiFlags::AlignCenter, true);
AddSText(0, 12, _("Buy basic items"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSText(0, 14, _("Buy premium items"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSText(0, 16, _("Sell items"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSText(0, 18, _("Repair items"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSText(0, 20, _("Leave the shop"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSLine(5);
storenumh = 20;
}
void ScrollSmithBuy(int idx)
{
ClearSText(5, 21);
stextup = 5;
for (int l = 5; l < 20; l += 4) {
if (!smithitem[idx].isEmpty()) {
UiFlags itemColor = smithitem[idx].getTextColorWithStatCheck();
if (smithitem[idx]._iMagical != ITEM_QUALITY_NORMAL) {
AddSText(20, l, smithitem[idx]._iIName, itemColor, true);
} else {
AddSText(20, l, smithitem[idx]._iName, itemColor, true);
}
AddSTextVal(l, smithitem[idx]._iIvalue);
PrintStoreItem(smithitem[idx], l + 1, itemColor);
stextdown = l;
idx++;
}
}
if (stextsel != -1 && !stext[stextsel]._ssel && stextsel != BackButtonLine())
stextsel = stextdown;
}
uint32_t TotalPlayerGold()
{
return MyPlayer->_pGold + Stash.gold;
}
// TODO: Change `_iIvalue` to be unsigned instead of passing `int` here.
bool PlayerCanAfford(int price)
{
return TotalPlayerGold() >= static_cast<uint32_t>(price);
}
void StartSmithBuy()
{
stextsize = true;
stextscrl = true;
stextsval = 0;
RenderGold = true;
AddSText(20, 1, _("I have these items for sale:"), UiFlags::ColorWhitegold, false);
AddSLine(3);
ScrollSmithBuy(stextsval);
AddItemListBackButton();
storenumh = 0;
for (Item &item : smithitem) {
if (item.isEmpty())
continue;
item._iStatFlag = MyPlayer->CanUseItem(item);
storenumh++;
}
stextsmax = std::max(storenumh - 4, 0);
}
void ScrollSmithPremiumBuy(int boughtitems)
{
ClearSText(5, 21);
stextup = 5;
int idx = 0;
for (; boughtitems != 0; idx++) {
if (!premiumitems[idx].isEmpty())
boughtitems--;
}
for (int l = 5; l < 20 && idx < SMITH_PREMIUM_ITEMS; l += 4) {
if (!premiumitems[idx].isEmpty()) {
UiFlags itemColor = premiumitems[idx].getTextColorWithStatCheck();
AddSText(20, l, premiumitems[idx]._iIName, itemColor, true);
AddSTextVal(l, premiumitems[idx]._iIvalue);
PrintStoreItem(premiumitems[idx], l + 1, itemColor);
stextdown = l;
} else {
l -= 4;
}
idx++;
}
if (stextsel != -1 && !stext[stextsel]._ssel && stextsel != BackButtonLine())
stextsel = stextdown;
}
bool StartSmithPremiumBuy()
{
storenumh = 0;
for (Item &item : premiumitems) {
if (item.isEmpty())
continue;
item._iStatFlag = MyPlayer->CanUseItem(item);
storenumh++;
}
if (storenumh == 0) {
StartStore(STORE_SMITH);
stextsel = 14;
return false;
}
stextsize = true;
stextscrl = true;
stextsval = 0;
RenderGold = true;
AddSText(20, 1, _("I have these premium items for sale:"), UiFlags::ColorWhitegold, false);
AddSLine(3);
AddItemListBackButton();
stextsmax = std::max(storenumh - 4, 0);
ScrollSmithPremiumBuy(stextsval);
return true;
}
bool SmithSellOk(int i)
{
Item *pI;
if (i >= 0) {
pI = &Players[MyPlayerId].InvList[i];
} else {
pI = &Players[MyPlayerId].SpdList[-(i + 1)];
}
if (pI->isEmpty())
return false;
if (pI->_iMiscId > IMISC_OILFIRST && pI->_iMiscId < IMISC_OILLAST)
return true;
if (pI->_itype == ItemType::Misc)
return false;
if (pI->_itype == ItemType::Gold)
return false;
if (pI->_itype == ItemType::Staff && (!gbIsHellfire || pI->_iSpell != SPL_NULL))
return false;
if (pI->_iClass == ICLASS_QUEST)
return false;
if (pI->IDidx == IDI_LAZSTAFF)
return false;
return true;
}
void ScrollSmithSell(int idx)
{
ClearSText(5, 21);
stextup = 5;
for (int l = 5; l < 20; l += 4) {
if (idx >= storenumh)
break;
if (!storehold[idx].isEmpty()) {
UiFlags itemColor = storehold[idx].getTextColorWithStatCheck();
if (storehold[idx]._iMagical != ITEM_QUALITY_NORMAL && storehold[idx]._iIdentified) {
AddSText(20, l, storehold[idx]._iIName, itemColor, true);
AddSTextVal(l, storehold[idx]._iIvalue);
} else {
AddSText(20, l, storehold[idx]._iName, itemColor, true);
AddSTextVal(l, storehold[idx]._ivalue);
}
PrintStoreItem(storehold[idx], l + 1, itemColor);
stextdown = l;
}
idx++;
}
stextsmax = std::max(storenumh - 4, 0);
}
void StartSmithSell()
{
stextsize = true;
bool sellOk = false;
storenumh = 0;
for (auto &item : storehold) {
item.clear();
}
const auto &myPlayer = Players[MyPlayerId];
for (int8_t i = 0; i < myPlayer._pNumInv; i++) {
if (storenumh >= 48)
break;
if (SmithSellOk(i)) {
sellOk = true;
storehold[storenumh] = myPlayer.InvList[i];
if (storehold[storenumh]._iMagical != ITEM_QUALITY_NORMAL && storehold[storenumh]._iIdentified)
storehold[storenumh]._ivalue = storehold[storenumh]._iIvalue;
storehold[storenumh]._ivalue = std::max(storehold[storenumh]._ivalue / 4, 1);
storehold[storenumh]._iIvalue = storehold[storenumh]._ivalue;
storehidx[storenumh] = i;
storenumh++;
}
}
for (int i = 0; i < MAXBELTITEMS; i++) {
if (storenumh >= 48)
break;
if (SmithSellOk(-(i + 1))) {
sellOk = true;
storehold[storenumh] = myPlayer.SpdList[i];
if (storehold[storenumh]._iMagical != ITEM_QUALITY_NORMAL && storehold[storenumh]._iIdentified)
storehold[storenumh]._ivalue = storehold[storenumh]._iIvalue;
storehold[storenumh]._ivalue = std::max(storehold[storenumh]._ivalue / 4, 1);
storehold[storenumh]._iIvalue = storehold[storenumh]._ivalue;
storehidx[storenumh] = -(i + 1);
storenumh++;
}
}
if (!sellOk) {
stextscrl = false;
RenderGold = true;
AddSText(20, 1, _("You have nothing I want."), UiFlags::ColorWhitegold, false);
AddSLine(3);
AddItemListBackButton(/*selectable=*/true);
return;
}
stextscrl = true;
stextsval = 0;
stextsmax = myPlayer._pNumInv;
RenderGold = true;
AddSText(20, 1, _("Which item is for sale?"), UiFlags::ColorWhitegold, false);
AddSLine(3);
ScrollSmithSell(stextsval);
AddItemListBackButton();
}
bool SmithRepairOk(int i)
{
const auto &myPlayer = Players[MyPlayerId];
if (myPlayer.InvList[i].isEmpty())
return false;
if (myPlayer.InvList[i]._itype == ItemType::Misc)
return false;
if (myPlayer.InvList[i]._itype == ItemType::Gold)
return false;
if (myPlayer.InvList[i]._iDurability == myPlayer.InvList[i]._iMaxDur)
return false;
return true;
}
void StartSmithRepair()
{
stextsize = true;
storenumh = 0;
for (auto &item : storehold) {
item.clear();
}
auto &myPlayer = Players[MyPlayerId];
auto &helmet = myPlayer.InvBody[INVLOC_HEAD];
if (!helmet.isEmpty() && helmet._iDurability != helmet._iMaxDur) {
AddStoreHoldRepair(&helmet, -1);
}
auto &armor = myPlayer.InvBody[INVLOC_CHEST];
if (!armor.isEmpty() && armor._iDurability != armor._iMaxDur) {
AddStoreHoldRepair(&armor, -2);
}
auto &leftHand = myPlayer.InvBody[INVLOC_HAND_LEFT];
if (!leftHand.isEmpty() && leftHand._iDurability != leftHand._iMaxDur) {
AddStoreHoldRepair(&leftHand, -3);
}
auto &rightHand = myPlayer.InvBody[INVLOC_HAND_RIGHT];
if (!rightHand.isEmpty() && rightHand._iDurability != rightHand._iMaxDur) {
AddStoreHoldRepair(&rightHand, -4);
}
for (int i = 0; i < myPlayer._pNumInv; i++) {
if (storenumh >= 48)
break;
if (SmithRepairOk(i)) {
AddStoreHoldRepair(&myPlayer.InvList[i], i);
}
}
if (storenumh == 0) {
stextscrl = false;
RenderGold = true;
AddSText(20, 1, _("You have nothing to repair."), UiFlags::ColorWhitegold, false);
AddSLine(3);
AddItemListBackButton(/*selectable=*/true);
return;
}
stextscrl = true;
stextsval = 0;
stextsmax = myPlayer._pNumInv;
RenderGold = true;
AddSText(20, 1, _("Repair which item?"), UiFlags::ColorWhitegold, false);
AddSLine(3);
ScrollSmithSell(stextsval);
AddItemListBackButton();
}
void FillManaPlayer()
{
if (!*sgOptions.Gameplay.adriaRefillsMana)
return;
auto &myPlayer = Players[MyPlayerId];
if (myPlayer._pMana != myPlayer._pMaxMana) {
PlaySFX(IS_CAST8);
}
myPlayer._pMana = myPlayer._pMaxMana;
myPlayer._pManaBase = myPlayer._pMaxManaBase;
drawmanaflag = true;
}
void StartWitch()
{
FillManaPlayer();
stextsize = false;
stextscrl = false;
AddSText(0, 2, _("Witch's shack"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 9, _("Would you like to:"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 12, _("Talk to Adria"), UiFlags::ColorBlue | UiFlags::AlignCenter, true);
AddSText(0, 14, _("Buy items"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSText(0, 16, _("Sell items"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSText(0, 18, _("Recharge staves"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSText(0, 20, _("Leave the shack"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSLine(5);
storenumh = 20;
}
void ScrollWitchBuy(int idx)
{
ClearSText(5, 21);
stextup = 5;
for (int l = 5; l < 20; l += 4) {
if (!witchitem[idx].isEmpty()) {
UiFlags itemColor = witchitem[idx].getTextColorWithStatCheck();
if (witchitem[idx]._iMagical != ITEM_QUALITY_NORMAL) {
AddSText(20, l, witchitem[idx]._iIName, itemColor, true);
} else {
AddSText(20, l, witchitem[idx]._iName, itemColor, true);
}
AddSTextVal(l, witchitem[idx]._iIvalue);
PrintStoreItem(witchitem[idx], l + 1, itemColor);
stextdown = l;
idx++;
}
}
if (stextsel != -1 && !stext[stextsel]._ssel && stextsel != BackButtonLine())
stextsel = stextdown;
}
void WitchBookLevel(Item &bookItem)
{
if (bookItem._iMiscId != IMISC_BOOK)
return;
bookItem._iMinMag = spelldata[bookItem._iSpell].sMinInt;
int8_t spellLevel = Players[MyPlayerId]._pSplLvl[bookItem._iSpell];
while (spellLevel > 0) {
bookItem._iMinMag += 20 * bookItem._iMinMag / 100;
spellLevel--;
if (bookItem._iMinMag + 20 * bookItem._iMinMag / 100 > 255) {
bookItem._iMinMag = 255;
spellLevel = 0;
}
}
}
void StartWitchBuy()
{
stextsize = true;
stextscrl = true;
stextsval = 0;
stextsmax = 20;
RenderGold = true;
AddSText(20, 1, _("I have these items for sale:"), UiFlags::ColorWhitegold, false);
AddSLine(3);
ScrollWitchBuy(stextsval);
AddItemListBackButton();
storenumh = 0;
for (Item &item : witchitem) {
if (item.isEmpty())
continue;
WitchBookLevel(item);
item._iStatFlag = MyPlayer->CanUseItem(item);
storenumh++;
}
stextsmax = std::max(storenumh - 4, 0);
}
bool WitchSellOk(int i)
{
Item *pI;
bool rv = false;
if (i >= 0)
pI = &Players[MyPlayerId].InvList[i];
else
pI = &Players[MyPlayerId].SpdList[-(i + 1)];
if (pI->_itype == ItemType::Misc)
rv = true;
if (pI->_iMiscId > 29 && pI->_iMiscId < 41)
rv = false;
if (pI->_iClass == ICLASS_QUEST)
rv = false;
if (pI->_itype == ItemType::Staff && (!gbIsHellfire || pI->_iSpell != SPL_NULL))
rv = true;
if (pI->IDidx >= IDI_FIRSTQUEST && pI->IDidx <= IDI_LASTQUEST)
rv = false;
if (pI->IDidx == IDI_LAZSTAFF)
rv = false;
return rv;
}
void StartWitchSell()
{
stextsize = true;
bool sellok = false;
storenumh = 0;
for (auto &item : storehold) {
item.clear();
}
const auto &myPlayer = Players[MyPlayerId];
for (int i = 0; i < myPlayer._pNumInv; i++) {
if (storenumh >= 48)
break;
if (WitchSellOk(i)) {
sellok = true;
storehold[storenumh] = myPlayer.InvList[i];
if (storehold[storenumh]._iMagical != ITEM_QUALITY_NORMAL && storehold[storenumh]._iIdentified)
storehold[storenumh]._ivalue = storehold[storenumh]._iIvalue;
storehold[storenumh]._ivalue = std::max(storehold[storenumh]._ivalue / 4, 1);
storehold[storenumh]._iIvalue = storehold[storenumh]._ivalue;
storehidx[storenumh] = i;
storenumh++;
}
}
for (int i = 0; i < MAXBELTITEMS; i++) {
if (storenumh >= 48)
break;
if (!myPlayer.SpdList[i].isEmpty() && WitchSellOk(-(i + 1))) {
sellok = true;
storehold[storenumh] = myPlayer.SpdList[i];
if (storehold[storenumh]._iMagical != ITEM_QUALITY_NORMAL && storehold[storenumh]._iIdentified)
storehold[storenumh]._ivalue = storehold[storenumh]._iIvalue;
storehold[storenumh]._ivalue = std::max(storehold[storenumh]._ivalue / 4, 1);
storehold[storenumh]._iIvalue = storehold[storenumh]._ivalue;
storehidx[storenumh] = -(i + 1);
storenumh++;
}
}
if (!sellok) {
stextscrl = false;
RenderGold = true;
AddSText(20, 1, _("You have nothing I want."), UiFlags::ColorWhitegold, false);
AddSLine(3);
AddItemListBackButton(/*selectable=*/true);
return;
}
stextscrl = true;
stextsval = 0;
stextsmax = myPlayer._pNumInv;
RenderGold = true;
AddSText(20, 1, _("Which item is for sale?"), UiFlags::ColorWhitegold, false);
AddSLine(3);
ScrollSmithSell(stextsval);
AddItemListBackButton();
}
bool WitchRechargeOk(int i)
{
const auto &item = Players[MyPlayerId].InvList[i];
if (item._itype == ItemType::Staff && item._iCharges != item._iMaxCharges) {
return true;
}
if ((item._iMiscId == IMISC_UNIQUE || item._iMiscId == IMISC_STAFF) && item._iCharges < item._iMaxCharges) {
return true;
}
return false;
}
void AddStoreHoldRecharge(Item itm, int8_t i)
{
storehold[storenumh] = itm;
storehold[storenumh]._ivalue += spelldata[itm._iSpell].sStaffCost;
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;
storenumh++;
}
void StartWitchRecharge()
{
stextsize = true;
bool rechargeok = false;
storenumh = 0;
for (auto &item : storehold) {
item.clear();
}
const auto &myPlayer = Players[MyPlayerId];
const auto &leftHand = myPlayer.InvBody[INVLOC_HAND_LEFT];
if ((leftHand._itype == ItemType::Staff || leftHand._iMiscId == IMISC_UNIQUE) && leftHand._iCharges != leftHand._iMaxCharges) {
rechargeok = true;
AddStoreHoldRecharge(leftHand, -1);
}
for (int i = 0; i < myPlayer._pNumInv; i++) {
if (storenumh >= 48)
break;
if (WitchRechargeOk(i)) {
rechargeok = true;
AddStoreHoldRecharge(myPlayer.InvList[i], i);
}
}
if (!rechargeok) {
stextscrl = false;
RenderGold = true;
AddSText(20, 1, _("You have nothing to recharge."), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSLine(3);
AddItemListBackButton(/*selectable=*/true);
return;
}
stextscrl = true;
stextsval = 0;
stextsmax = myPlayer._pNumInv;
RenderGold = true;
AddSText(20, 1, _("Recharge which item?"), UiFlags::ColorWhitegold, false);
AddSLine(3);
ScrollSmithSell(stextsval);
AddItemListBackButton();
}
void StoreNoMoney()
{
StartStore(stextshold);
stextscrl = false;
stextsize = true;
RenderGold = true;
ClearSText(5, 23);
AddSText(0, 14, _("You do not have enough gold"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
}
void StoreNoRoom()
{
StartStore(stextshold);
stextscrl = false;
ClearSText(5, 23);
AddSText(0, 14, _("You do not have enough room in inventory"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
}
void StoreConfirm(Item &item)
{
StartStore(stextshold);
stextscrl = false;
ClearSText(5, 23);
UiFlags itemColor = item.getTextColorWithStatCheck();
bool idprint = item._iMagical != ITEM_QUALITY_NORMAL;
if (stextshold == STORE_SIDENTIFY)
idprint = false;
if (item._iMagical != ITEM_QUALITY_NORMAL && !item._iIdentified) {
if (stextshold == STORE_SSELL)
idprint = false;
if (stextshold == STORE_WSELL)
idprint = false;
if (stextshold == STORE_SREPAIR)
idprint = false;
if (stextshold == STORE_WRECHARGE)
idprint = false;
}
if (idprint)
AddSText(20, 8, item._iIName, itemColor, false);
else
AddSText(20, 8, item._iName, itemColor, false);
AddSTextVal(8, item._iIvalue);
PrintStoreItem(item, 9, itemColor);
string_view prompt;
switch (stextshold) {
case STORE_BBOY:
prompt = _("Do we have a deal?");
break;
case STORE_SIDENTIFY:
prompt = _("Are you sure you want to identify this item?");
break;
case STORE_HBUY:
case STORE_SPBUY:
case STORE_WBUY:
case STORE_SBUY:
prompt = _("Are you sure you want to buy this item?");
break;
case STORE_WRECHARGE:
prompt = _("Are you sure you want to recharge this item?");
break;
case STORE_SSELL:
case STORE_WSELL:
prompt = _("Are you sure you want to sell this item?");
break;
case STORE_SREPAIR:
prompt = _("Are you sure you want to repair this item?");
break;
default:
app_fatal("Unknown store dialog %i", stextshold);
}
AddSText(0, 15, prompt, UiFlags::ColorWhite | UiFlags::AlignCenter, false);
AddSText(0, 18, _("Yes"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSText(0, 20, _("No"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
}
void StartBoy()
{
stextsize = false;
stextscrl = false;
AddSText(0, 2, _("Wirt the Peg-legged boy"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSLine(5);
if (!boyitem.isEmpty()) {
AddSText(0, 8, _("Talk to Wirt"), UiFlags::ColorBlue | UiFlags::AlignCenter, true);
AddSText(0, 12, _("I have something for sale,"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 14, _("but it will cost 50 gold"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 16, _("just to take a look. "), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 18, _("What have you got?"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSText(0, 20, _("Say goodbye"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
} else {
AddSText(0, 12, _("Talk to Wirt"), UiFlags::ColorBlue | UiFlags::AlignCenter, true);
AddSText(0, 18, _("Say goodbye"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
}
}
void SStartBoyBuy()
{
stextsize = true;
stextscrl = false;
RenderGold = true;
AddSText(20, 1, _("I have this item for sale:"), UiFlags::ColorWhitegold, false);
AddSLine(3);
boyitem._iStatFlag = MyPlayer->CanUseItem(boyitem);
UiFlags itemColor = boyitem.getTextColorWithStatCheck();
if (boyitem._iMagical != ITEM_QUALITY_NORMAL)
AddSText(20, 10, boyitem._iIName, itemColor, true);
else
AddSText(20, 10, boyitem._iName, itemColor, true);
if (gbIsHellfire)
AddSTextVal(10, boyitem._iIvalue - (boyitem._iIvalue / 4));
else
AddSTextVal(10, boyitem._iIvalue + (boyitem._iIvalue / 2));
PrintStoreItem(boyitem, 11, itemColor);
{
// Add a Leave button. Unlike the other item list back buttons,
// this one has different text and different layout in LargerSmallFont locales.
const int line = BackButtonLine();
AddSLine(line - 1);
AddSText(0, line, _("Leave"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
stext[line]._syoff = 6;
}
}
void HealPlayer()
{
auto &myPlayer = Players[MyPlayerId];
if (myPlayer._pHitPoints != myPlayer._pMaxHP) {
PlaySFX(IS_CAST8);
}
myPlayer._pHitPoints = myPlayer._pMaxHP;
myPlayer._pHPBase = myPlayer._pMaxHPBase;
drawhpflag = true;
}
void StartHealer()
{
HealPlayer();
stextsize = false;
stextscrl = false;
AddSText(0, 1, _("Welcome to the"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 3, _("Healer's home"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 9, _("Would you like to:"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 12, _("Talk to Pepin"), UiFlags::ColorBlue | UiFlags::AlignCenter, true);
AddSText(0, 14, _("Buy items"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSText(0, 18, _("Leave Healer's home"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSLine(5);
storenumh = 20;
}
void ScrollHealerBuy(int idx)
{
ClearSText(5, 21);
stextup = 5;
for (int l = 5; l < 20; l += 4) {
if (!healitem[idx].isEmpty()) {
UiFlags itemColor = healitem[idx].getTextColorWithStatCheck();
AddSText(20, l, healitem[idx]._iName, itemColor, true);
AddSTextVal(l, healitem[idx]._iIvalue);
PrintStoreItem(healitem[idx], l + 1, itemColor);
stextdown = l;
idx++;
}
}
if (stextsel != -1 && !stext[stextsel]._ssel && stextsel != BackButtonLine())
stextsel = stextdown;
}
void StartHealerBuy()
{
stextsize = true;
stextscrl = true;
stextsval = 0;
RenderGold = true;
AddSText(20, 1, _("I have these items for sale:"), UiFlags::ColorWhitegold, false);
AddSLine(3);
ScrollHealerBuy(stextsval);
AddItemListBackButton();
storenumh = 0;
for (Item &item : healitem) {
if (item.isEmpty())
continue;
item._iStatFlag = MyPlayer->CanUseItem(item);
storenumh++;
}
stextsmax = std::max(storenumh - 4, 0);
}
void StartStoryteller()
{
stextsize = false;
stextscrl = false;
AddSText(0, 2, _("The Town Elder"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 9, _("Would you like to:"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 12, _("Talk to Cain"), UiFlags::ColorBlue | UiFlags::AlignCenter, true);
AddSText(0, 14, _("Identify an item"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSText(0, 18, _("Say goodbye"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSLine(5);
}
bool IdItemOk(Item *i)
{
if (i->isEmpty()) {
return false;
}
if (i->_iMagical == ITEM_QUALITY_NORMAL) {
return false;
}
return !i->_iIdentified;
}
void AddStoreHoldId(Item itm, int8_t i)
{
storehold[storenumh] = itm;
storehold[storenumh]._ivalue = 100;
storehold[storenumh]._iIvalue = 100;
storehidx[storenumh] = i;
storenumh++;
}
void StartStorytellerIdentify()
{
bool idok = false;
stextsize = true;
storenumh = 0;
for (auto &item : storehold) {
item.clear();
}
auto &myPlayer = Players[MyPlayerId];
auto &helmet = myPlayer.InvBody[INVLOC_HEAD];
if (IdItemOk(&helmet)) {
idok = true;
AddStoreHoldId(helmet, -1);
}
auto &armor = myPlayer.InvBody[INVLOC_CHEST];
if (IdItemOk(&armor)) {
idok = true;
AddStoreHoldId(armor, -2);
}
auto &leftHand = myPlayer.InvBody[INVLOC_HAND_LEFT];
if (IdItemOk(&leftHand)) {
idok = true;
AddStoreHoldId(leftHand, -3);
}
auto &rightHand = myPlayer.InvBody[INVLOC_HAND_RIGHT];
if (IdItemOk(&rightHand)) {
idok = true;
AddStoreHoldId(rightHand, -4);
}
auto &leftRing = myPlayer.InvBody[INVLOC_RING_LEFT];
if (IdItemOk(&leftRing)) {
idok = true;
AddStoreHoldId(leftRing, -5);
}
auto &rightRing = myPlayer.InvBody[INVLOC_RING_RIGHT];
if (IdItemOk(&rightRing)) {
idok = true;
AddStoreHoldId(rightRing, -6);
}
auto &amulet = myPlayer.InvBody[INVLOC_AMULET];
if (IdItemOk(&amulet)) {
idok = true;
AddStoreHoldId(amulet, -7);
}
for (int i = 0; i < myPlayer._pNumInv; i++) {
if (storenumh >= 48)
break;
auto &item = myPlayer.InvList[i];
if (IdItemOk(&item)) {
idok = true;
AddStoreHoldId(item, i);
}
}
if (!idok) {
stextscrl = false;
RenderGold = true;
AddSText(20, 1, _("You have nothing to identify."), UiFlags::ColorWhitegold, false);
AddSLine(3);
AddItemListBackButton(/*selectable=*/true);
return;
}
stextscrl = true;
stextsval = 0;
stextsmax = myPlayer._pNumInv;
RenderGold = true;
AddSText(20, 1, _("Identify which item?"), UiFlags::ColorWhitegold, false);
AddSLine(3);
ScrollSmithSell(stextsval);
AddItemListBackButton();
}
void StartStorytellerIdentifyShow(Item &item)
{
StartStore(stextshold);
stextscrl = false;
ClearSText(5, 23);
UiFlags itemColor = item.getTextColorWithStatCheck();
AddSText(0, 7, _("This item is:"), UiFlags::ColorWhite | UiFlags::AlignCenter, false);
AddSText(20, 11, item._iIName, itemColor, false);
PrintStoreItem(item, 12, itemColor);
AddSText(0, 18, _("Done"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
}
void StartTalk()
{
int la;
stextsize = false;
stextscrl = false;
AddSText(0, 2, fmt::format(_("Talk to {:s}"), _(TownerNames[talker])), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSLine(5);
if (gbIsSpawn) {
AddSText(0, 10, fmt::format(_("Talking to {:s}"), _(TownerNames[talker])), UiFlags::ColorWhite | UiFlags::AlignCenter, false);
AddSText(0, 12, _("is not available"), UiFlags::ColorWhite | UiFlags::AlignCenter, false);
AddSText(0, 14, _("in the shareware"), UiFlags::ColorWhite | UiFlags::AlignCenter, false);
AddSText(0, 16, _("version"), UiFlags::ColorWhite | UiFlags::AlignCenter, false);
AddOptionsBackButton();
return;
}
int sn = 0;
for (auto &quest : Quests) {
if (quest._qactive == QUEST_ACTIVE && QuestDialogTable[talker][quest._qidx] != TEXT_NONE && quest._qlog)
sn++;
}
if (sn > 6) {
sn = 14 - (sn / 2);
la = 1;
} else {
sn = 15 - sn;
la = 2;
}
int sn2 = sn - 2;
for (auto &quest : Quests) {
if (quest._qactive == QUEST_ACTIVE && QuestDialogTable[talker][quest._qidx] != TEXT_NONE && quest._qlog) {
AddSText(0, sn, _(QuestsData[quest._qidx]._qlstr), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
sn += la;
}
}
AddSText(0, sn2, _("Gossip"), UiFlags::ColorBlue | UiFlags::AlignCenter, true);
AddOptionsBackButton();
}
void StartTavern()
{
stextsize = false;
stextscrl = false;
AddSText(0, 1, _("Welcome to the"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 3, _("Rising Sun"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 9, _("Would you like to:"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 12, _("Talk to Ogden"), UiFlags::ColorBlue | UiFlags::AlignCenter, true);
AddSText(0, 18, _("Leave the tavern"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSLine(5);
storenumh = 20;
}
void StartBarmaid()
{
stextsize = false;
stextscrl = false;
AddSText(0, 2, _("Gillian"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 9, _("Would you like to:"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 12, _("Talk to Gillian"), UiFlags::ColorBlue | UiFlags::AlignCenter, true);
AddSText(0, 14, _("Access Storage"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSText(0, 18, _("Say goodbye"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSLine(5);
storenumh = 20;
}
void StartDrunk()
{
stextsize = false;
stextscrl = false;
AddSText(0, 2, _("Farnham the Drunk"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 9, _("Would you like to:"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false);
AddSText(0, 12, _("Talk to Farnham"), UiFlags::ColorBlue | UiFlags::AlignCenter, true);
AddSText(0, 18, _("Say Goodbye"), UiFlags::ColorWhite | UiFlags::AlignCenter, true);
AddSLine(5);
storenumh = 20;
}
void SmithEnter()
{
switch (stextsel) {
case 10:
talker = TOWN_SMITH;
stextlhold = 10;
stextshold = STORE_SMITH;
gossipstart = TEXT_GRISWOLD2;
gossipend = TEXT_GRISWOLD13;
StartStore(STORE_GOSSIP);
break;
case 12:
StartStore(STORE_SBUY);
break;
case 14:
StartStore(STORE_SPBUY);
break;
case 16:
StartStore(STORE_SSELL);
break;
case 18:
StartStore(STORE_SREPAIR);
break;
case 20:
stextflag = STORE_NONE;
break;
}
}
/**
* @brief Purchases an item from the smith.
*/
void SmithBuyItem(Item &item)
{
TakePlrsMoney(item._iIvalue);
if (item._iMagical == ITEM_QUALITY_NORMAL)
item._iIdentified = false;
StoreAutoPlace(item, true);
int idx = stextvhold + ((stextlhold - stextup) / 4);
if (idx == SMITH_ITEMS - 1) {
smithitem[SMITH_ITEMS - 1].clear();
} else {
for (; !smithitem[idx + 1].isEmpty(); idx++) {
smithitem[idx] = std::move(smithitem[idx + 1]);
}
smithitem[idx].clear();
}
CalcPlrInv(*MyPlayer, true);
}
void SmithBuyEnter()
{
if (stextsel == BackButtonLine()) {
StartStore(STORE_SMITH);
stextsel = 12;
return;
}
stextlhold = stextsel;
stextvhold = stextsval;
stextshold = STORE_SBUY;
int idx = stextsval + ((stextsel - stextup) / 4);
if (!PlayerCanAfford(smithitem[idx]._iIvalue)) {
StartStore(STORE_NOMONEY);
return;
}
if (!StoreAutoPlace(smithitem[idx], false)) {
StartStore(STORE_NOROOM);
return;
}
StoreItem = smithitem[idx];
StartStore(STORE_CONFIRM);
}
/**
* @brief Purchases a premium item from the smith.
*/
void SmithBuyPItem(Item &item)
{
TakePlrsMoney(item._iIvalue);
if (item._iMagical == ITEM_QUALITY_NORMAL)
item._iIdentified = false;
StoreAutoPlace(item, true);
int idx = stextvhold + ((stextlhold - stextup) / 4);
int xx = 0;
for (int i = 0; idx >= 0; i++) {
if (!premiumitems[i].isEmpty()) {
idx--;
xx = i;
}
}
premiumitems[xx].clear();
numpremium--;
SpawnPremium(MyPlayerId);
}
void SmithPremiumBuyEnter()
{
if (stextsel == BackButtonLine()) {
StartStore(STORE_SMITH);
stextsel = 14;
return;
}
stextshold = STORE_SPBUY;
stextlhold = stextsel;
stextvhold = stextsval;
int xx = stextsval + ((stextsel - stextup) / 4);
int idx = 0;
for (int i = 0; xx >= 0; i++) {
if (!premiumitems[i].isEmpty()) {
xx--;
idx = i;
}
}
if (!PlayerCanAfford(premiumitems[idx]._iIvalue)) {
StartStore(STORE_NOMONEY);
return;
}
if (!StoreAutoPlace(premiumitems[idx], false)) {
StartStore(STORE_NOROOM);
return;
}
StoreItem = premiumitems[idx];
StartStore(STORE_CONFIRM);
}
bool StoreGoldFit(Item &item)
{
int cost = item._iIvalue;
Size itemSize = GetInventorySize(item);
int itemRoomForGold = itemSize.width * itemSize.height * MaxGold;
if (cost <= itemRoomForGold) {
return true;
}
return cost <= itemRoomForGold + RoomForGold();
}
/**
* @brief Sells an item from the player's inventory or belt.
*/
void StoreSellItem()
{
auto &myPlayer = Players[MyPlayerId];
int idx = stextvhold + ((stextlhold - stextup) / 4);
if (storehidx[idx] >= 0)
myPlayer.RemoveInvItem(storehidx[idx]);
else
myPlayer.RemoveSpdBarItem(-(storehidx[idx] + 1));
int cost = storehold[idx]._iIvalue;
storenumh--;
if (idx != storenumh) {
while (idx < storenumh) {
storehold[idx] = storehold[idx + 1];
storehidx[idx] = storehidx[idx + 1];
idx++;
}
}
AddGoldToInventory(myPlayer, cost);
myPlayer._pGold += cost;
}
void SmithSellEnter()
{
if (stextsel == BackButtonLine()) {
StartStore(STORE_SMITH);
stextsel = 16;
return;
}
stextlhold = stextsel;
stextshold = STORE_SSELL;
stextvhold = stextsval;
int idx = stextsval + ((stextsel - stextup) / 4);
if (!StoreGoldFit(storehold[idx])) {
StartStore(STORE_NOROOM);
return;
}
StoreItem = storehold[idx];
StartStore(STORE_CONFIRM);
}
/**
* @brief Repairs an item in the player's inventory or body in the smith.
*/
void SmithRepairItem(int price)
{
TakePlrsMoney(price);
int idx = stextvhold + ((stextlhold - stextup) / 4);
storehold[idx]._iDurability = storehold[idx]._iMaxDur;
int8_t i = storehidx[idx];
auto &myPlayer = *MyPlayer;
if (i < 0) {
if (i == -1)
myPlayer.InvBody[INVLOC_HEAD]._iDurability = myPlayer.InvBody[INVLOC_HEAD]._iMaxDur;
if (i == -2)
myPlayer.InvBody[INVLOC_CHEST]._iDurability = myPlayer.InvBody[INVLOC_CHEST]._iMaxDur;
if (i == -3)
myPlayer.InvBody[INVLOC_HAND_LEFT]._iDurability = myPlayer.InvBody[INVLOC_HAND_LEFT]._iMaxDur;
if (i == -4)
myPlayer.InvBody[INVLOC_HAND_RIGHT]._iDurability = myPlayer.InvBody[INVLOC_HAND_RIGHT]._iMaxDur;
return;
}
myPlayer.InvList[i]._iDurability = myPlayer.InvList[i]._iMaxDur;
}
void SmithRepairEnter()
{
if (stextsel == BackButtonLine()) {
StartStore(STORE_SMITH);
stextsel = 18;
return;
}
stextshold = STORE_SREPAIR;
stextlhold = stextsel;
stextvhold = stextsval;
int idx = stextsval + ((stextsel - stextup) / 4);
if (!PlayerCanAfford(storehold[idx]._iIvalue)) {
StartStore(STORE_NOMONEY);
return;
}
StoreItem = storehold[idx];
StartStore(STORE_CONFIRM);
}
void WitchEnter()
{
switch (stextsel) {
case 12:
stextlhold = 12;
talker = TOWN_WITCH;
stextshold = STORE_WITCH;
gossipstart = TEXT_ADRIA2;
gossipend = TEXT_ADRIA13;
StartStore(STORE_GOSSIP);
break;
case 14:
StartStore(STORE_WBUY);
break;
case 16:
StartStore(STORE_WSELL);
break;
case 18:
StartStore(STORE_WRECHARGE);
break;
case 20:
stextflag = STORE_NONE;
break;
}
}
/**
* @brief Purchases an item from the witch.
*/
void WitchBuyItem(Item &item)
{
int idx = stextvhold + ((stextlhold - stextup) / 4);
if (idx < 3)
item._iSeed = AdvanceRndSeed();
TakePlrsMoney(item._iIvalue);
StoreAutoPlace(item, true);
if (idx >= 3) {
if (idx == WITCH_ITEMS - 1) {
witchitem[WITCH_ITEMS - 1].clear();
} else {
for (; !witchitem[idx + 1].isEmpty(); idx++) {
witchitem[idx] = std::move(witchitem[idx + 1]);
}
witchitem[idx].clear();
}
}
CalcPlrInv(*MyPlayer, true);
}
void WitchBuyEnter()
{
if (stextsel == BackButtonLine()) {
StartStore(STORE_WITCH);
stextsel = 14;
return;
}
stextlhold = stextsel;
stextvhold = stextsval;
stextshold = STORE_WBUY;
int idx = stextsval + ((stextsel - stextup) / 4);
if (!PlayerCanAfford(witchitem[idx]._iIvalue)) {
StartStore(STORE_NOMONEY);
return;
}
if (!StoreAutoPlace(witchitem[idx], false)) {
StartStore(STORE_NOROOM);
return;
}
StoreItem = witchitem[idx];
StartStore(STORE_CONFIRM);
}
void WitchSellEnter()
{
if (stextsel == BackButtonLine()) {
StartStore(STORE_WITCH);
stextsel = 16;
return;
}
stextlhold = stextsel;
stextshold = STORE_WSELL;
stextvhold = stextsval;
int idx = stextsval + ((stextsel - stextup) / 4);
if (!StoreGoldFit(storehold[idx])) {
StartStore(STORE_NOROOM);
return;
}
StoreItem = storehold[idx];
StartStore(STORE_CONFIRM);
}
/**
* @brief Recharges an item in the player's inventory or body in the witch.
*/
void WitchRechargeItem(int price)
{
TakePlrsMoney(price);
int idx = stextvhold + ((stextlhold - stextup) / 4);
storehold[idx]._iCharges = storehold[idx]._iMaxCharges;
auto &myPlayer = *MyPlayer;
int8_t i = storehidx[idx];
if (i < 0)
myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges = myPlayer.InvBody[INVLOC_HAND_LEFT]._iMaxCharges;
else
myPlayer.InvList[i]._iCharges = myPlayer.InvList[i]._iMaxCharges;
CalcPlrInv(myPlayer, true);
}
void WitchRechargeEnter()
{
if (stextsel == BackButtonLine()) {
StartStore(STORE_WITCH);
stextsel = 18;
return;
}
stextshold = STORE_WRECHARGE;
stextlhold = stextsel;
stextvhold = stextsval;
int idx = stextsval + ((stextsel - stextup) / 4);
if (!PlayerCanAfford(storehold[idx]._iIvalue)) {
StartStore(STORE_NOMONEY);
return;
}
StoreItem = storehold[idx];
StartStore(STORE_CONFIRM);
}
void BoyEnter()
{
if (!boyitem.isEmpty() && stextsel == 18) {
if (!PlayerCanAfford(50)) {
stextshold = STORE_BOY;
stextlhold = 18;
stextvhold = stextsval;
StartStore(STORE_NOMONEY);
} else {
TakePlrsMoney(50);
StartStore(STORE_BBOY);
}
return;
}
if ((stextsel != 8 && !boyitem.isEmpty()) || (stextsel != 12 && boyitem.isEmpty())) {
stextflag = STORE_NONE;
return;
}
talker = TOWN_PEGBOY;
stextshold = STORE_BOY;
stextlhold = stextsel;
gossipstart = TEXT_WIRT2;
gossipend = TEXT_WIRT12;
StartStore(STORE_GOSSIP);
}
void BoyBuyItem(Item &item)
{
TakePlrsMoney(item._iIvalue);
StoreAutoPlace(item, true);
boyitem.clear();
stextshold = STORE_BOY;
CalcPlrInv(*MyPlayer, true);
stextlhold = 12;
}
/**
* @brief Purchases an item from the healer.
*/
void HealerBuyItem(Item &item)
{
int idx = stextvhold + ((stextlhold - stextup) / 4);
if (!gbIsMultiplayer) {
if (idx < 2)
item._iSeed = AdvanceRndSeed();
} else {
if (idx < 3)
item._iSeed = AdvanceRndSeed();
}
TakePlrsMoney(item._iIvalue);
if (item._iMagical == ITEM_QUALITY_NORMAL)
item._iIdentified = false;
StoreAutoPlace(item, true);
if (!gbIsMultiplayer) {
if (idx < 2)
return;
} else {
if (idx < 3)
return;
}
idx = stextvhold + ((stextlhold - stextup) / 4);
if (idx == 19) {
healitem[19].clear();
} else {
for (; !healitem[idx + 1].isEmpty(); idx++) {
healitem[idx] = std::move(healitem[idx + 1]);
}
healitem[idx].clear();
}
CalcPlrInv(*MyPlayer, true);
}
void BoyBuyEnter()
{
if (stextsel != 10) {
stextflag = STORE_NONE;
return;
}
stextshold = STORE_BBOY;
stextvhold = stextsval;
stextlhold = 10;
int price = boyitem._iIvalue;
if (gbIsHellfire)
price -= boyitem._iIvalue / 4;
else
price += boyitem._iIvalue / 2;
if (TotalPlayerGold() < price) {
StartStore(STORE_NOMONEY);
return;
}
if (!StoreAutoPlace(boyitem, false)) {
StartStore(STORE_NOROOM);
return;
}
StoreItem = boyitem;
StoreItem._iIvalue = price;
StartStore(STORE_CONFIRM);
}
void StorytellerIdentifyItem(Item &item)
{
auto &myPlayer = Players[MyPlayerId];
int8_t idx = storehidx[((stextlhold - stextup) / 4) + stextvhold];
if (idx < 0) {
if (idx == -1)
myPlayer.InvBody[INVLOC_HEAD]._iIdentified = true;
if (idx == -2)
myPlayer.InvBody[INVLOC_CHEST]._iIdentified = true;
if (idx == -3)
myPlayer.InvBody[INVLOC_HAND_LEFT]._iIdentified = true;
if (idx == -4)
myPlayer.InvBody[INVLOC_HAND_RIGHT]._iIdentified = true;
if (idx == -5)
myPlayer.InvBody[INVLOC_RING_LEFT]._iIdentified = true;
if (idx == -6)
myPlayer.InvBody[INVLOC_RING_RIGHT]._iIdentified = true;
if (idx == -7)
myPlayer.InvBody[INVLOC_AMULET]._iIdentified = true;
} else {
myPlayer.InvList[idx]._iIdentified = true;
}
item._iIdentified = true;
TakePlrsMoney(item._iIvalue);
CalcPlrInv(myPlayer, true);
}
void ConfirmEnter(Item &item)
{
if (stextsel == 18) {
switch (stextshold) {
case STORE_SBUY:
SmithBuyItem(item);
break;
case STORE_SSELL:
case STORE_WSELL:
StoreSellItem();
break;
case STORE_SREPAIR:
SmithRepairItem(item._iIvalue);
break;
case STORE_WBUY:
WitchBuyItem(item);
break;
case STORE_WRECHARGE:
WitchRechargeItem(item._iIvalue);
break;
case STORE_BBOY:
BoyBuyItem(item);
break;
case STORE_HBUY:
HealerBuyItem(item);
break;
case STORE_SIDENTIFY:
StorytellerIdentifyItem(item);
StartStore(STORE_IDSHOW);
return;
case STORE_SPBUY:
SmithBuyPItem(item);
break;
default:
break;
}
}
StartStore(stextshold);
if (stextsel == BackButtonLine())
return;
stextsel = stextlhold;
stextsval = std::min(stextvhold, stextsmax);
while (stextsel != -1 && !stext[stextsel]._ssel) {
stextsel--;
}
}
void HealerEnter()
{
switch (stextsel) {
case 12:
stextlhold = 12;
talker = TOWN_HEALER;
stextshold = STORE_HEALER;
gossipstart = TEXT_PEPIN2;
gossipend = TEXT_PEPIN11;
StartStore(STORE_GOSSIP);
break;
case 14:
StartStore(STORE_HBUY);
break;
case 18:
stextflag = STORE_NONE;
break;
}
}
void HealerBuyEnter()
{
if (stextsel == BackButtonLine()) {
StartStore(STORE_HEALER);
stextsel = 14;
return;
}
stextlhold = stextsel;
stextvhold = stextsval;
stextshold = STORE_HBUY;
int idx = stextsval + ((stextsel - stextup) / 4);
if (TotalPlayerGold() < healitem[idx]._iIvalue) {
StartStore(STORE_NOMONEY);
return;
}
if (!StoreAutoPlace(healitem[idx], false)) {
StartStore(STORE_NOROOM);
return;
}
StoreItem = healitem[idx];
StartStore(STORE_CONFIRM);
}
void StorytellerEnter()
{
switch (stextsel) {
case 12:
stextlhold = 12;
talker = TOWN_STORY;
stextshold = STORE_STORY;
gossipstart = TEXT_STORY2;
gossipend = TEXT_STORY11;
StartStore(STORE_GOSSIP);
break;
case 14:
StartStore(STORE_SIDENTIFY);
break;
case 18:
stextflag = STORE_NONE;
break;
}
}
void StorytellerIdentifyEnter()
{
if (stextsel == BackButtonLine()) {
StartStore(STORE_STORY);
stextsel = 14;
return;
}
stextshold = STORE_SIDENTIFY;
stextlhold = stextsel;
stextvhold = stextsval;
int idx = stextsval + ((stextsel - stextup) / 4);
if (!PlayerCanAfford(storehold[idx]._iIvalue)) {
StartStore(STORE_NOMONEY);
return;
}
StoreItem = storehold[idx];
StartStore(STORE_CONFIRM);
}
void TalkEnter()
{
if (stextsel == BackButtonLine()) {
StartStore(stextshold);
stextsel = stextlhold;
return;
}
int sn = 0;
for (auto &quest : Quests) {
if (quest._qactive == QUEST_ACTIVE && QuestDialogTable[talker][quest._qidx] != TEXT_NONE && quest._qlog)
sn++;
}
int la = 2;
if (sn > 6) {
sn = 14 - (sn / 2);
la = 1;
} else {
sn = 15 - sn;
}
if (stextsel == sn - 2) {
SetRndSeed(Towners[talker].seed);
auto tq = static_cast<_speech_id>(gossipstart + GenerateRnd(gossipend - gossipstart + 1));
InitQTextMsg(tq);
return;
}
for (auto &quest : Quests) {
if (quest._qactive == QUEST_ACTIVE && QuestDialogTable[talker][quest._qidx] != TEXT_NONE && quest._qlog) {
if (sn == stextsel) {
InitQTextMsg(QuestDialogTable[talker][quest._qidx]);
}
sn += la;
}
}
}
void TavernEnter()
{
switch (stextsel) {
case 12:
stextlhold = 12;
talker = TOWN_TAVERN;
stextshold = STORE_TAVERN;
gossipstart = TEXT_OGDEN2;
gossipend = TEXT_OGDEN10;
StartStore(STORE_GOSSIP);
break;
case 18:
stextflag = STORE_NONE;
break;
}
}
void BarmaidEnter()
{
switch (stextsel) {
case 12:
stextlhold = 12;
talker = TOWN_BMAID;
stextshold = STORE_BARMAID;
gossipstart = TEXT_GILLIAN2;
gossipend = TEXT_GILLIAN10;
StartStore(STORE_GOSSIP);
break;
case 14:
stextflag = STORE_NONE;
IsStashOpen = true;
Stash.RefreshItemStatFlags();
invflag = true;
if (ControlMode != ControlTypes::KeyboardAndMouse) {
if (pcurs == CURSOR_DISARM)
NewCursor(CURSOR_HAND);
FocusOnInventory();
}
break;
case 18:
stextflag = STORE_NONE;
break;
}
}
void DrunkEnter()
{
switch (stextsel) {
case 12:
stextlhold = 12;
talker = TOWN_DRUNK;
stextshold = STORE_DRUNK;
gossipstart = TEXT_FARNHAM2;
gossipend = TEXT_FARNHAM13;
StartStore(STORE_GOSSIP);
break;
case 18:
stextflag = STORE_NONE;
break;
}
}
int TakeGold(Player &player, int cost, bool skipMaxPiles)
{
for (int i = 0; i < player._pNumInv; i++) {
auto &item = player.InvList[i];
if (item._itype != ItemType::Gold || (skipMaxPiles && item._ivalue == MaxGold))
continue;
if (cost < item._ivalue) {
item._ivalue -= cost;
SetPlrHandGoldCurs(player.InvList[i]);
return 0;
}
cost -= item._ivalue;
player.RemoveInvItem(i);
i = -1;
}
return cost;
}
void DrawSelector(const Surface &out, const Rectangle &rect, const char *text, UiFlags flags)
{
int lineWidth = GetLineWidth(text);
int x1 = rect.position.x - 20;
if (HasAnyOf(flags, UiFlags::AlignCenter))
x1 += (rect.size.width - lineWidth) / 2;
CelDrawTo(out, { x1, rect.position.y + 13 }, *pSPentSpn2Cels, PentSpn2Spin());
int x2 = rect.position.x + rect.size.width + 5;
if (HasAnyOf(flags, UiFlags::AlignCenter))
x2 = rect.position.x + (rect.size.width - lineWidth) / 2 + lineWidth + 5;
CelDrawTo(out, { x2, rect.position.y + 13 }, *pSPentSpn2Cels, PentSpn2Spin());
}
} // namespace
void AddStoreHoldRepair(Item *itm, int8_t i)
{
Item *item;
int v;
item = &storehold[storenumh];
storehold[storenumh] = *itm;
int due = item->_iMaxDur - item->_iDurability;
if (item->_iMagical != ITEM_QUALITY_NORMAL && item->_iIdentified) {
v = 30 * item->_iIvalue * due / (item->_iMaxDur * 100 * 2);
if (v == 0)
return;
} else {
v = item->_ivalue * due / (item->_iMaxDur * 2);
v = std::max(v, 1);
}
item->_iIvalue = v;
item->_ivalue = v;
storehidx[storenumh] = i;
storenumh++;
}
void InitStores()
{
ClearSText(0, STORE_LINES);
stextflag = STORE_NONE;
stextsize = false;
stextscrl = false;
numpremium = 0;
premiumlevel = 1;
for (auto &premiumitem : premiumitems)
premiumitem.clear();
boyitem.clear();
boylevel = 0;
}
void SetupTownStores()
{
auto &myPlayer = Players[MyPlayerId];
int l = myPlayer._pLevel / 2;
if (!gbIsMultiplayer) {
l = 0;
for (int i = 0; i < NUMLEVELS; i++) {
if (myPlayer._pLvlVisited[i])
l = i;
}
} else {
SetRndSeed(glSeedTbl[currlevel] * SDL_GetTicks());
}
l = clamp(l + 2, 6, 16);
SpawnSmith(l);
SpawnWitch(l);
SpawnHealer(l);
SpawnBoy(myPlayer._pLevel);
SpawnPremium(MyPlayerId);
}
void FreeStoreMem()
{
stextflag = STORE_NONE;
}
void PrintSString(const Surface &out, int margin, int line, const char *text, UiFlags flags, int price)
{
int sx = PANEL_X + 32 + margin;
if (!stextsize) {
sx += 320;
}
const int sy = UI_OFFSET_Y + PaddingTop + stext[line].y + stext[line]._syoff;
int width = stextsize ? 575 : 255;
if (stextscrl && line >= 4 && line <= 20) {
width -= 9; // Space for the selector
}
width -= margin * 2;
const Rectangle rect { { sx, sy }, { width, 0 } };
DrawString(out, text, rect, flags);
if (price > 0) {
char valstr[32];
sprintf(valstr, "%i", price);
DrawString(out, valstr, rect, flags | UiFlags::AlignRight);
}
if (stextsel == line) {
DrawSelector(out, rect, text, flags);
}
}
void DrawSLine(const Surface &out, int sy)
{
int sx = 26;
int width = 587;
if (!stextsize) {
sx += SPANEL_WIDTH;
width -= SPANEL_WIDTH;
}
BYTE *src = out.at(PANEL_LEFT + sx, UI_OFFSET_Y + 25);
BYTE *dst = out.at(PANEL_X + sx, sy);
for (int i = 0; i < 3; i++, src += out.pitch(), dst += out.pitch())
memcpy(dst, src, width);
}
void DrawSTextHelp()
{
stextsel = -1;
stextsize = true;
}
void ClearSText(int s, int e)
{
for (int i = s; i < e; i++) {
stext[i]._sx = 0;
stext[i]._syoff = 0;
stext[i]._sstr[0] = 0;
stext[i].flags = UiFlags::None;
stext[i]._sline = 0;
stext[i]._ssel = false;
stext[i]._sval = 0;
}
}
void StartStore(talk_id s)
{
sbookflag = false;
CloseInventory();
chrflag = false;
RenderGold = false;
QuestLogIsOpen = false;
CloseGoldDrop();
ClearSText(0, STORE_LINES);
ReleaseStoreBtn();
switch (s) {
case STORE_SMITH:
StartSmith();
break;
case STORE_SBUY: {
bool hasAnyItems = false;
for (int i = 0; !smithitem[i].isEmpty(); i++) {
hasAnyItems = true;
break;
}
if (hasAnyItems)
StartSmithBuy();
else {
stextflag = STORE_SBUY;
stextlhold = 12;
StoreESC();
return;
}
break;
}
case STORE_SSELL:
StartSmithSell();
break;
case STORE_SREPAIR:
StartSmithRepair();
break;
case STORE_WITCH:
StartWitch();
break;
case STORE_WBUY:
if (storenumh > 0)
StartWitchBuy();
break;
case STORE_WSELL:
StartWitchSell();
break;
case STORE_WRECHARGE:
StartWitchRecharge();
break;
case STORE_NOMONEY:
StoreNoMoney();
break;
case STORE_NOROOM:
StoreNoRoom();
break;
case STORE_CONFIRM:
StoreConfirm(StoreItem);
break;
case STORE_BOY:
StartBoy();
break;
case STORE_BBOY:
SStartBoyBuy();
break;
case STORE_HEALER:
StartHealer();
break;
case STORE_STORY:
StartStoryteller();
break;
case STORE_HBUY:
if (storenumh > 0)
StartHealerBuy();
break;
case STORE_SIDENTIFY:
StartStorytellerIdentify();
break;
case STORE_SPBUY:
if (!StartSmithPremiumBuy())
return;
break;
case STORE_GOSSIP:
StartTalk();
break;
case STORE_IDSHOW:
StartStorytellerIdentifyShow(StoreItem);
break;
case STORE_TAVERN:
StartTavern();
break;
case STORE_DRUNK:
StartDrunk();
break;
case STORE_BARMAID:
StartBarmaid();
break;
case STORE_NONE:
break;
}
stextsel = -1;
for (int i = 0; i < STORE_LINES; i++) {
if (stext[i]._ssel) {
stextsel = i;
break;
}
}
stextflag = s;
}
void DrawSText(const Surface &out)
{
if (!stextsize)
DrawSTextBack(out);
else
DrawQTextBack(out);
if (stextscrl) {
switch (stextflag) {
case STORE_SBUY:
ScrollSmithBuy(stextsval);
break;
case STORE_SSELL:
case STORE_SREPAIR:
case STORE_WSELL:
case STORE_WRECHARGE:
case STORE_SIDENTIFY:
ScrollSmithSell(stextsval);
break;
case STORE_WBUY:
ScrollWitchBuy(stextsval);
break;
case STORE_HBUY:
ScrollHealerBuy(stextsval);
break;
case STORE_SPBUY:
ScrollSmithPremiumBuy(stextsval);
break;
default:
break;
}
}
CalculateLineHeights();
for (int i = 0; i < STORE_LINES; i++) {
if (stext[i].IsDivider())
DrawSLine(out, UI_OFFSET_Y + PaddingTop + stext[i].y + TextHeight() / 2);
if (stext[i].IsText())
PrintSString(out, stext[i]._sx, i, stext[i]._sstr, stext[i].flags, stext[i]._sval);
}
if (RenderGold) {
PrintSString(out, 28, 1, fmt::format(_("Your gold: {:d}"), TotalPlayerGold()).c_str(), UiFlags::ColorWhitegold | UiFlags::AlignRight);
}
if (stextscrl)
DrawSSlider(out, 4, 20);
}
void StoreESC()
{
if (qtextflag) {
qtextflag = false;
if (leveltype == DTYPE_TOWN)
stream_stop();
return;
}
switch (stextflag) {
case STORE_SMITH:
case STORE_WITCH:
case STORE_BOY:
case STORE_BBOY:
case STORE_HEALER:
case STORE_STORY:
case STORE_TAVERN:
case STORE_DRUNK:
case STORE_BARMAID:
stextflag = STORE_NONE;
break;
case STORE_GOSSIP:
StartStore(stextshold);
stextsel = stextlhold;
break;
case STORE_SBUY:
StartStore(STORE_SMITH);
stextsel = 12;
break;
case STORE_SPBUY:
StartStore(STORE_SMITH);
stextsel = 14;
break;
case STORE_SSELL:
StartStore(STORE_SMITH);
stextsel = 16;
break;
case STORE_SREPAIR:
StartStore(STORE_SMITH);
stextsel = 18;
break;
case STORE_WBUY:
StartStore(STORE_WITCH);
stextsel = 14;
break;
case STORE_WSELL:
StartStore(STORE_WITCH);
stextsel = 16;
break;
case STORE_WRECHARGE:
StartStore(STORE_WITCH);
stextsel = 18;
break;
case STORE_HBUY:
StartStore(STORE_HEALER);
stextsel = 14;
break;
case STORE_SIDENTIFY:
StartStore(STORE_STORY);
stextsel = 14;
break;
case STORE_IDSHOW:
StartStore(STORE_SIDENTIFY);
break;
case STORE_NOMONEY:
case STORE_NOROOM:
case STORE_CONFIRM:
StartStore(stextshold);
stextsel = stextlhold;
stextsval = stextvhold;
break;
case STORE_NONE:
break;
}
}
void StoreUp()
{
PlaySFX(IS_TITLEMOV);
if (stextsel == -1) {
return;
}
if (stextscrl) {
if (stextsel == stextup) {
if (stextsval != 0)
stextsval--;
return;
}
stextsel--;
while (!stext[stextsel]._ssel) {
if (stextsel == 0)
stextsel = STORE_LINES - 1;
else
stextsel--;
}
return;
}
if (stextsel == 0)
stextsel = STORE_LINES - 1;
else
stextsel--;
while (!stext[stextsel]._ssel) {
if (stextsel == 0)
stextsel = STORE_LINES - 1;
else
stextsel--;
}
}
void StoreDown()
{
PlaySFX(IS_TITLEMOV);
if (stextsel == -1) {
return;
}
if (stextscrl) {
if (stextsel == stextdown) {
if (stextsval < stextsmax)
stextsval++;
return;
}
stextsel++;
while (!stext[stextsel]._ssel) {
if (stextsel == STORE_LINES - 1)
stextsel = 0;
else
stextsel++;
}
return;
}
if (stextsel == STORE_LINES - 1)
stextsel = 0;
else
stextsel++;
while (!stext[stextsel]._ssel) {
if (stextsel == STORE_LINES - 1)
stextsel = 0;
else
stextsel++;
}
}
void StorePrior()
{
PlaySFX(IS_TITLEMOV);
if (stextsel != -1 && stextscrl) {
if (stextsel == stextup) {
stextsval = std::max(stextsval - 4, 0);
} else {
stextsel = stextup;
}
}
}
void StoreNext()
{
PlaySFX(IS_TITLEMOV);
if (stextsel != -1 && stextscrl) {
if (stextsel == stextdown) {
if (stextsval < stextsmax)
stextsval += 4;
if (stextsval > stextsmax)
stextsval = stextsmax;
} else {
stextsel = stextdown;
}
}
}
void TakePlrsMoney(int cost)
{
auto &myPlayer = Players[MyPlayerId];
myPlayer._pGold -= std::min(cost, myPlayer._pGold);
cost = TakeGold(myPlayer, cost, true);
if (cost != 0) {
cost = TakeGold(myPlayer, cost, false);
}
Stash.gold -= cost;
Stash.dirty = true;
}
void StoreEnter()
{
if (qtextflag) {
qtextflag = false;
if (leveltype == DTYPE_TOWN)
stream_stop();
return;
}
PlaySFX(IS_TITLSLCT);
switch (stextflag) {
case STORE_SMITH:
SmithEnter();
break;
case STORE_SPBUY:
SmithPremiumBuyEnter();
break;
case STORE_SBUY:
SmithBuyEnter();
break;
case STORE_SSELL:
SmithSellEnter();
break;
case STORE_SREPAIR:
SmithRepairEnter();
break;
case STORE_WITCH:
WitchEnter();
break;
case STORE_WBUY:
WitchBuyEnter();
break;
case STORE_WSELL:
WitchSellEnter();
break;
case STORE_WRECHARGE:
WitchRechargeEnter();
break;
case STORE_NOMONEY:
case STORE_NOROOM:
StartStore(stextshold);
stextsel = stextlhold;
stextsval = stextvhold;
break;
case STORE_CONFIRM:
ConfirmEnter(StoreItem);
break;
case STORE_BOY:
BoyEnter();
break;
case STORE_BBOY:
BoyBuyEnter();
break;
case STORE_HEALER:
HealerEnter();
break;
case STORE_STORY:
StorytellerEnter();
break;
case STORE_HBUY:
HealerBuyEnter();
break;
case STORE_SIDENTIFY:
StorytellerIdentifyEnter();
break;
case STORE_GOSSIP:
TalkEnter();
break;
case STORE_IDSHOW:
StartStore(STORE_SIDENTIFY);
break;
case STORE_DRUNK:
DrunkEnter();
break;
case STORE_TAVERN:
TavernEnter();
break;
case STORE_BARMAID:
BarmaidEnter();
break;
case STORE_NONE:
break;
}
}
void CheckStoreBtn()
{
if (qtextflag) {
qtextflag = false;
if (leveltype == DTYPE_TOWN)
stream_stop();
} else if (stextsel != -1 && MousePosition.y >= (PaddingTop + UI_OFFSET_Y) && MousePosition.y <= (320 + UI_OFFSET_Y)) {
if (!stextsize) {
if (MousePosition.x < 344 + PANEL_LEFT || MousePosition.x > 616 + PANEL_LEFT)
return;
} else {
if (MousePosition.x < 24 + PANEL_LEFT || MousePosition.x > 616 + PANEL_LEFT)
return;
}
const int relativeY = MousePosition.y - (UI_OFFSET_Y + PaddingTop);
if (stextscrl && MousePosition.x > 600 + PANEL_LEFT) {
// Scroll bar is always measured in terms of the small line height.
int y = relativeY / SmallLineHeight;
if (y == 4) {
if (stextscrlubtn <= 0) {
StoreUp();
stextscrlubtn = 10;
} else {
stextscrlubtn--;
}
}
if (y == 20) {
if (stextscrldbtn <= 0) {
StoreDown();
stextscrldbtn = 10;
} else {
stextscrldbtn--;
}
}
return;
}
int y = relativeY / LineHeight();
// Large small fonts draw beyond LineHeight. Check if the click was on the overflow text.
if (IsSmallFontTall() && y > 0 && y < STORE_LINES
&& stext[y - 1].IsText() && !stext[y].IsText()
&& relativeY < stext[y - 1].y + LargeTextHeight) {
--y;
}
if (y >= 5) {
if (y >= BackButtonLine() + 1)
y = BackButtonLine();
if (stextscrl && y <= 20 && !stext[y]._ssel) {
if (stext[y - 2]._ssel) {
y -= 2;
} else if (stext[y - 1]._ssel) {
y--;
}
}
if (stext[y]._ssel || (stextscrl && y == BackButtonLine())) {
stextsel = y;
StoreEnter();
}
}
}
}
void ReleaseStoreBtn()
{
stextscrlubtn = -1;
stextscrldbtn = -1;
}
} // namespace devilution