|
|
|
|
/**
|
|
|
|
|
* @file qol.cpp
|
|
|
|
|
*
|
|
|
|
|
* Quality of life features
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
|
|
#include "DiabloUI/art_draw.h"
|
|
|
|
|
#include "control.h"
|
|
|
|
|
#include "cursor.h"
|
|
|
|
|
#include "options.h"
|
|
|
|
|
#include "qol/common.h"
|
|
|
|
|
#include "qol/xpbar.h"
|
|
|
|
|
|
|
|
|
|
namespace devilution {
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
struct QolArt {
|
|
|
|
|
Art healthBox;
|
|
|
|
|
Art resistance;
|
|
|
|
|
Art health;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
QolArt *qolArt = nullptr;
|
|
|
|
|
|
|
|
|
|
int GetTextWidth(const char *s)
|
|
|
|
|
{
|
|
|
|
|
int l = 0;
|
|
|
|
|
while (*s) {
|
|
|
|
|
l += fontkern[fontframe[gbFontTransTbl[(BYTE)*s++]]] + 1;
|
|
|
|
|
}
|
|
|
|
|
return l;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FastDrawVertLine(CelOutputBuffer out, int x, int y, int height, BYTE col)
|
|
|
|
|
{
|
|
|
|
|
BYTE *p = out.at(x, y);
|
|
|
|
|
for (int j = 0; j < height; j++) {
|
|
|
|
|
*p = col;
|
|
|
|
|
p += out.pitch();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
void FreeQol()
|
|
|
|
|
{
|
|
|
|
|
delete qolArt;
|
|
|
|
|
qolArt = nullptr;
|
|
|
|
|
|
|
|
|
|
FreeXPBar();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void InitQol()
|
|
|
|
|
{
|
|
|
|
|
if (sgOptions.Gameplay.bEnemyHealthBar) {
|
|
|
|
|
qolArt = new QolArt();
|
|
|
|
|
LoadMaskedArt("data\\healthbox.pcx", &qolArt->healthBox, 1, 1);
|
|
|
|
|
LoadArt("data\\health.pcx", &qolArt->health);
|
|
|
|
|
LoadMaskedArt("data\\resistance.pcx", &qolArt->resistance, 6, 1);
|
|
|
|
|
|
|
|
|
|
if ((qolArt->healthBox.surface == nullptr)
|
|
|
|
|
|| (qolArt->health.surface == nullptr)
|
|
|
|
|
|| (qolArt->resistance.surface == nullptr)) {
|
|
|
|
|
app_fatal("Failed to load UI resources. Is devilutionx.mpq accessible and up to date?");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InitXPBar();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DrawMonsterHealthBar(const CelOutputBuffer &out)
|
|
|
|
|
{
|
|
|
|
|
if (!sgOptions.Gameplay.bEnemyHealthBar)
|
|
|
|
|
return;
|
|
|
|
|
assert(qolArt != nullptr);
|
|
|
|
|
assert(qolArt->healthBox.surface != nullptr);
|
|
|
|
|
assert(qolArt->health.surface != nullptr);
|
|
|
|
|
assert(qolArt->resistance.surface != nullptr);
|
|
|
|
|
if (currlevel == 0)
|
|
|
|
|
return;
|
|
|
|
|
if (pcursmonst == -1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
MonsterStruct *mon = &monster[pcursmonst];
|
|
|
|
|
|
|
|
|
|
int width = qolArt->healthBox.w();
|
|
|
|
|
int height = qolArt->healthBox.h();
|
|
|
|
|
int xPos = (gnScreenWidth - width) / 2;
|
|
|
|
|
|
|
|
|
|
if (PANELS_COVER) {
|
|
|
|
|
if (invflag || sbookflag)
|
|
|
|
|
xPos -= SPANEL_WIDTH / 2;
|
|
|
|
|
if (chrflag || questlog)
|
|
|
|
|
xPos += SPANEL_WIDTH / 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int yPos = 18;
|
|
|
|
|
int border = 3;
|
|
|
|
|
|
|
|
|
|
const int maxLife = std::max(mon->_mmaxhp, mon->_mhitpoints);
|
|
|
|
|
|
|
|
|
|
DrawArt(out, xPos, yPos, &qolArt->healthBox);
|
|
|
|
|
DrawHalfTransparentRectTo(out, xPos + border, yPos + border, width - (border * 2), height - (border * 2));
|
|
|
|
|
int barProgress = (width * mon->_mhitpoints) / maxLife;
|
|
|
|
|
if (barProgress) {
|
|
|
|
|
DrawArt(out, xPos + border + 1, yPos + border + 1, &qolArt->health, 0, barProgress, height - (border * 2) - 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sgOptions.Gameplay.bShowMonsterType) {
|
|
|
|
|
Uint8 borderColors[] = { 248 /*undead*/, 232 /*demon*/, 150 /*beast*/ };
|
|
|
|
|
Uint8 borderColor = borderColors[mon->MData->mMonstClass];
|
|
|
|
|
int borderWidth = width - (border * 2);
|
|
|
|
|
FastDrawHorizLine(out, xPos + border, yPos + border, borderWidth, borderColor);
|
|
|
|
|
FastDrawHorizLine(out, xPos + border, yPos + height - border - 1, borderWidth, borderColor);
|
|
|
|
|
int borderHeight = height - (border * 2) - 2;
|
|
|
|
|
FastDrawVertLine(out, xPos + border, yPos + border + 1, borderHeight, borderColor);
|
|
|
|
|
FastDrawVertLine(out, xPos + width - border - 1, yPos + border + 1, borderHeight, borderColor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int barLableX = xPos + width / 2 - GetTextWidth(mon->mName) / 2;
|
|
|
|
|
int barLableY = yPos + 10 + (height - 11) / 2;
|
|
|
|
|
PrintGameStr(out, barLableX - 1, barLableY + 1, mon->mName, COL_BLACK);
|
|
|
|
|
text_color color = COL_WHITE;
|
|
|
|
|
if (mon->_uniqtype != 0)
|
|
|
|
|
color = COL_GOLD;
|
|
|
|
|
else if (mon->leader != 0)
|
|
|
|
|
color = COL_BLUE;
|
|
|
|
|
PrintGameStr(out, barLableX, barLableY, mon->mName, color);
|
|
|
|
|
|
|
|
|
|
if (mon->_uniqtype != 0 || monstkills[mon->MType->mtype] >= 15) {
|
|
|
|
|
monster_resistance immunes[] = { IMMUNE_MAGIC, IMMUNE_FIRE, IMMUNE_LIGHTNING };
|
|
|
|
|
monster_resistance resists[] = { RESIST_MAGIC, RESIST_FIRE, RESIST_LIGHTNING };
|
|
|
|
|
|
|
|
|
|
int resOffset = 5;
|
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
|
if (mon->mMagicRes & immunes[i]) {
|
|
|
|
|
DrawArt(out, xPos + resOffset, yPos + height - 6, &qolArt->resistance, i * 2 + 1);
|
|
|
|
|
resOffset += qolArt->resistance.w() + 2;
|
|
|
|
|
} else if (mon->mMagicRes & resists[i]) {
|
|
|
|
|
DrawArt(out, xPos + resOffset, yPos + height - 6, &qolArt->resistance, i * 2);
|
|
|
|
|
resOffset += qolArt->resistance.w() + 2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool HasRoomForGold()
|
|
|
|
|
{
|
|
|
|
|
for (int idx : plr[myplr].InvGrid) {
|
|
|
|
|
// Secondary item cell. No need to check those as we'll go through the main item cells anyway.
|
|
|
|
|
if (idx < 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Empty cell. 1x1 space available.
|
|
|
|
|
if (idx == 0)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
// Main item cell. Potentially a gold pile so check it.
|
|
|
|
|
auto item = plr[myplr].InvList[idx - 1];
|
|
|
|
|
if (item._itype == ITYPE_GOLD && item._ivalue < MaxGold)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AutoGoldPickup(int pnum)
|
|
|
|
|
{
|
|
|
|
|
if (!sgOptions.Gameplay.bAutoGoldPickup)
|
|
|
|
|
return;
|
|
|
|
|
if (pnum != myplr)
|
|
|
|
|
return;
|
|
|
|
|
if (leveltype == DTYPE_TOWN)
|
|
|
|
|
return;
|
|
|
|
|
if (!HasRoomForGold())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for (int dir = 0; dir < 8; dir++) {
|
|
|
|
|
int x = plr[pnum].position.tile.x + pathxdir[dir];
|
|
|
|
|
int y = plr[pnum].position.tile.y + pathydir[dir];
|
|
|
|
|
if (dItem[x][y] != 0) {
|
|
|
|
|
int itemIndex = dItem[x][y] - 1;
|
|
|
|
|
if (items[itemIndex]._itype == ITYPE_GOLD) {
|
|
|
|
|
NetSendCmdGItem(true, CMD_REQUESTAGITEM, pnum, pnum, itemIndex);
|
|
|
|
|
items[itemIndex]._iRequest = true;
|
|
|
|
|
PlaySFX(IS_IGRAB);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace devilution
|