1 /**
2  * @file qol.cpp
3  *
4  * Quality of life features
5  */
6 #include "all.h"
7 #include "options.h"
8 #include "DiabloUI/art_draw.h"
9 
10 DEVILUTION_BEGIN_NAMESPACE
11 namespace {
12 
13 struct QolArt {
14 	Art healthBox;
15 	Art resistance;
16 	Art health;
17 };
18 
19 QolArt *qolArt = nullptr;
20 
GetTextWidth(const char * s)21 int GetTextWidth(const char *s)
22 {
23 	int l = 0;
24 	while (*s) {
25 		l += fontkern[fontframe[gbFontTransTbl[(BYTE)*s++]]] + 1;
26 	}
27 	return l;
28 }
29 
FastDrawHorizLine(CelOutputBuffer out,int x,int y,int width,BYTE col)30 void FastDrawHorizLine(CelOutputBuffer out, int x, int y, int width, BYTE col)
31 {
32 	memset(out.at(x, y), col, width);
33 }
34 
FastDrawVertLine(CelOutputBuffer out,int x,int y,int height,BYTE col)35 void FastDrawVertLine(CelOutputBuffer out, int x, int y, int height, BYTE col)
36 {
37 	BYTE *p = out.at(x, y);
38 	for (int j = 0; j < height; j++) {
39 		*p = col;
40 		p += out.pitch();
41 	}
42 }
43 
FillRect(CelOutputBuffer out,int x,int y,int width,int height,BYTE col)44 void FillRect(CelOutputBuffer out, int x, int y, int width, int height, BYTE col)
45 {
46 	for (int j = 0; j < height; j++) {
47 		FastDrawHorizLine(out, x, y + j, width, col);
48 	}
49 }
50 
51 } // namespace
52 
FreeQol()53 void FreeQol()
54 {
55 	delete qolArt;
56 	qolArt = nullptr;
57 }
58 
InitQol()59 void InitQol()
60 {
61 	if (sgOptions.Gameplay.bEnemyHealthBar) {
62 		qolArt = new QolArt();
63 		LoadMaskedArt("data\\healthbox.pcx", &qolArt->healthBox, 1, 1);
64 		LoadArt("data\\health.pcx", &qolArt->health);
65 		LoadMaskedArt("data\\resistance.pcx", &qolArt->resistance, 6, 1);
66 
67 		if ((qolArt->healthBox.surface == nullptr)
68 		    || (qolArt->health.surface == nullptr)
69 		    || (qolArt->resistance.surface == nullptr)) {
70 			app_fatal("Failed to load UI resources. Is devilutionx.mpq accessible and up to date?");
71 		}
72 	}
73 }
74 
DrawMonsterHealthBar(CelOutputBuffer out)75 void DrawMonsterHealthBar(CelOutputBuffer out)
76 {
77 	if (!sgOptions.Gameplay.bEnemyHealthBar)
78 		return;
79 	assert(qolArt != nullptr);
80 	assert(qolArt->healthBox.surface != nullptr);
81 	assert(qolArt->health.surface != nullptr);
82 	assert(qolArt->resistance.surface != nullptr);
83 	if (currlevel == 0)
84 		return;
85 	if (pcursmonst == -1)
86 		return;
87 
88 	MonsterStruct *mon = &monster[pcursmonst];
89 
90 	Sint32 width = qolArt->healthBox.w();
91 	Sint32 height = qolArt->healthBox.h();
92 	Sint32 xPos = (gnScreenWidth - width) / 2;
93 
94 	if (PANELS_COVER) {
95 		if (invflag || sbookflag)
96 			xPos -= SPANEL_WIDTH / 2;
97 		if (chrflag || questlog)
98 			xPos += SPANEL_WIDTH / 2;
99 	}
100 
101 	Sint32 yPos = 18;
102 	Sint32 border = 3;
103 
104 	Sint32 maxLife = mon->_mmaxhp;
105 	if (mon->_mhitpoints > maxLife)
106 		maxLife = mon->_mhitpoints;
107 
108 	DrawArt(out, xPos, yPos, &qolArt->healthBox);
109 	DrawHalfTransparentRectTo(out, xPos + border, yPos + border, width - (border * 2), height - (border * 2));
110 	int barProgress = (width * mon->_mhitpoints) / maxLife;
111 	if (barProgress) {
112 		DrawArt(out, xPos + border + 1, yPos + border + 1, &qolArt->health, 0, barProgress, height - (border * 2) - 2);
113 	}
114 
115 	if (sgOptions.Gameplay.bShowMonsterType) {
116 		Uint8 borderColors[] = { 248 /*undead*/, 232 /*demon*/, 150 /*beast*/ };
117 		Uint8 borderColor = borderColors[mon->MData->mMonstClass];
118 		Sint32 borderWidth = width - (border * 2);
119 		FastDrawHorizLine(out, xPos + border, yPos + border, borderWidth, borderColor);
120 		FastDrawHorizLine(out, xPos + border, yPos + height - border - 1, borderWidth, borderColor);
121 		Sint32 borderHeight = height - (border * 2) - 2;
122 		FastDrawVertLine(out, xPos + border, yPos + border + 1, borderHeight, borderColor);
123 		FastDrawVertLine(out, xPos + width - border - 1, yPos + border + 1, borderHeight, borderColor);
124 	}
125 
126 	Sint32 barLableX = xPos + width / 2 - GetTextWidth(mon->mName) / 2;
127 	Sint32 barLableY = yPos + 10 + (height - 11) / 2;
128 	PrintGameStr(out, barLableX - 1, barLableY + 1, mon->mName, COL_BLACK);
129 	text_color color = COL_WHITE;
130 	if (mon->_uniqtype != 0)
131 		color = COL_GOLD;
132 	else if (mon->leader != 0)
133 		color = COL_BLUE;
134 	PrintGameStr(out, barLableX, barLableY, mon->mName, color);
135 
136 	if (mon->_uniqtype != 0 || monstkills[mon->MType->mtype] >= 15) {
137 		monster_resistance immunes[] = { IMMUNE_MAGIC, IMMUNE_FIRE, IMMUNE_LIGHTNING };
138 		monster_resistance resists[] = { RESIST_MAGIC, RESIST_FIRE, RESIST_LIGHTNING };
139 
140 		Sint32 resOffset = 5;
141 		for (Sint32 i = 0; i < 3; i++) {
142 			if (mon->mMagicRes & immunes[i]) {
143 				DrawArt(out, xPos + resOffset, yPos + height - 6, &qolArt->resistance, i * 2 + 1);
144 				resOffset += qolArt->resistance.w() + 2;
145 			} else if (mon->mMagicRes & resists[i]) {
146 				DrawArt(out, xPos + resOffset, yPos + height - 6, &qolArt->resistance, i * 2);
147 				resOffset += qolArt->resistance.w() + 2;
148 			}
149 		}
150 	}
151 }
152 
DrawXPBar(CelOutputBuffer out)153 void DrawXPBar(CelOutputBuffer out)
154 {
155 	if (!sgOptions.Gameplay.bExperienceBar)
156 		return;
157 
158 	int barWidth = 306;
159 	int barHeight = 5;
160 	int yPos = gnScreenHeight - 9;                 // y position of xp bar
161 	int xPos = (gnScreenWidth - barWidth) / 2 + 5; // x position of xp bar
162 	int dividerHeight = 3;
163 	int numDividers = 10;
164 	int barColor = 198;
165 	int emptyBarColor = 0;
166 	int frameColor = 196;
167 	bool space = true; // add 1 pixel separator on top/bottom of the bar
168 
169 	PrintGameStr(out, xPos - 22, yPos + 6, "XP", COL_WHITE);
170 	int charLevel = plr[myplr]._pLevel;
171 	if (charLevel == MAXCHARLEVEL - 1)
172 		return;
173 
174 	int prevXp = ExpLvlsTbl[charLevel - 1];
175 	if (plr[myplr]._pExperience < prevXp)
176 		return;
177 
178 	Uint64 prevXpDelta_1 = plr[myplr]._pExperience - prevXp;
179 	int prevXpDelta = ExpLvlsTbl[charLevel] - prevXp;
180 	int visibleBar = barWidth * prevXpDelta_1 / prevXpDelta;
181 
182 	FillRect(out, xPos, yPos, barWidth, barHeight, emptyBarColor);
183 	FastDrawHorizLine(out, xPos - 1, yPos - 1, barWidth + 2, frameColor);
184 	FastDrawHorizLine(out, xPos - 1, yPos + barHeight, barWidth + 2, frameColor);
185 	FastDrawVertLine(out, xPos - 1, yPos - 1, barHeight + 2, frameColor);
186 	FastDrawVertLine(out, xPos + barWidth, yPos - 1, barHeight + 2, frameColor);
187 	for (int i = 1; i < numDividers; i++)
188 		FastDrawVertLine(out, xPos - 1 + (barWidth * i / numDividers), yPos - dividerHeight + 3, barHeight, 245);
189 
190 	FillRect(out, xPos, yPos + (space ? 1 : 0), visibleBar, barHeight - (space ? 2 : 0), barColor);
191 }
192 
HasRoomForGold()193 bool HasRoomForGold()
194 {
195 	for (int i = 0; i < NUM_INV_GRID_ELEM; i++) {
196 		int idx = plr[myplr].InvGrid[i];
197 
198 		// Secondary item cell. No need to check those as we'll go through the main item cells anyway.
199 		if (idx < 0)
200 			continue;
201 
202 		// Empty cell. 1x1 space available.
203 		if (idx == 0)
204 			return true;
205 
206 		// Main item cell. Potentially a gold pile so check it.
207 		auto item = plr[myplr].InvList[idx - 1];
208 		if (item._itype == ITYPE_GOLD && item._ivalue < MaxGold)
209 			return true;
210 	}
211 
212 	return false;
213 }
214 
AutoGoldPickup(int pnum)215 void AutoGoldPickup(int pnum)
216 {
217 	if (!sgOptions.Gameplay.bAutoGoldPickup)
218 		return;
219 	if (pnum != myplr)
220 		return;
221 	if (leveltype == DTYPE_TOWN)
222 		return;
223 	if (!HasRoomForGold())
224 		return;
225 
226 	for (int dir = 0; dir < 8; dir++) {
227 		int x = plr[pnum]._px + pathxdir[dir];
228 		int y = plr[pnum]._py + pathydir[dir];
229 		if (dItem[x][y] != 0) {
230 			int itemIndex = dItem[x][y] - 1;
231 			if (item[itemIndex]._itype == ITYPE_GOLD) {
232 				NetSendCmdGItem(TRUE, CMD_REQUESTAGITEM, pnum, pnum, itemIndex);
233 				item[itemIndex]._iRequest = TRUE;
234 				PlaySFX(IS_IGRAB);
235 			}
236 		}
237 	}
238 }
239 
240 DEVILUTION_END_NAMESPACE
241