1 /**
2  * @file control.cpp
3  *
4  * Implementation of the character and main control panels
5  */
6 #include "all.h"
7 
8 #include <cstddef>
9 
10 DEVILUTION_BEGIN_NAMESPACE
11 
12 namespace {
13 
14 CelOutputBuffer pBtmBuff;
15 CelOutputBuffer pLifeBuff;
16 CelOutputBuffer pManaBuff;
17 
18 } // namespace
19 
20 BYTE sgbNextTalkSave;
21 BYTE sgbTalkSavePos;
22 BYTE *pDurIcons;
23 BYTE *pChrButtons;
24 BOOL drawhpflag;
25 BOOL dropGoldFlag;
26 BOOL panbtn[8];
27 BOOL chrbtn[4];
28 BYTE *pMultiBtns;
29 BYTE *pPanelButtons;
30 BYTE *pChrPanel;
31 BOOL lvlbtndown;
32 char sgszTalkSave[8][80];
33 int dropGoldValue;
34 BOOL drawmanaflag;
35 BOOL chrbtnactive;
36 char sgszTalkMsg[MAX_SEND_STR_LEN];
37 BYTE *pPanelText;
38 BYTE *pTalkBtns;
39 BOOL pstrjust[4];
40 int pnumlines;
41 BOOL pinfoflag;
42 BOOL talkbtndown[3];
43 spell_id pSpell;
44 text_color infoclr;
45 int sgbPlrTalkTbl;
46 BYTE *pGBoxBuff;
47 BYTE *pSBkBtnCel;
48 char tempstr[256];
49 BOOLEAN whisper[MAX_PLRS];
50 int sbooktab;
51 spell_type pSplType;
52 int initialDropGoldIndex;
53 BOOL talkflag;
54 BYTE *pSBkIconCels;
55 BOOL sbookflag;
56 BOOL chrflag;
57 BOOL drawbtnflag;
58 BYTE *pSpellBkCel;
59 char infostr[64];
60 int numpanbtns;
61 char panelstr[4][64];
62 BOOL panelflag;
63 BYTE SplTransTbl[256];
64 int initialDropGoldValue;
65 BYTE *pSpellCels;
66 BOOL panbtndown;
67 BOOL spselflag;
68 
69 /** Map of hero class names */
70 const char *const ClassStrTbl[] = {
71 	"Warrior",
72 	"Rogue",
73 	"Sorcerer",
74 	"Monk",
75 	"Bard",
76 	"Barbarian",
77 };
78 
79 /** Maps from font index to smaltext.cel frame number. */
80 const BYTE fontframe[128] = {
81 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
82 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
83 	0, 54, 44, 57, 58, 56, 55, 47, 40, 41, 59, 39, 50, 37, 51, 52,
84 	36, 27, 28, 29, 30, 31, 32, 33, 34, 35, 48, 49, 60, 38, 61, 53,
85 	62, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
86 	16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 42, 63, 43, 64, 65,
87 	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
88 	16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 40, 66, 41, 67, 0
89 };
90 
91 /**
92  * Maps from smaltext.cel frame number to character width. Note, the
93  * character width may be distinct from the frame width, which is 13 for every
94  * smaltext.cel frame.
95  */
96 const BYTE fontkern[68] = {
97 	8, 10, 7, 9, 8, 7, 6, 8, 8, 3,
98 	3, 8, 6, 11, 9, 10, 6, 9, 9, 6,
99 	9, 11, 10, 13, 10, 11, 7, 5, 7, 7,
100 	8, 7, 7, 7, 7, 7, 10, 4, 5, 6,
101 	3, 3, 4, 3, 6, 6, 3, 3, 3, 3,
102 	3, 2, 7, 6, 3, 10, 10, 6, 6, 7,
103 	4, 4, 9, 6, 6, 12, 3, 7
104 };
105 /**
106  * Line start position for info box text when displaying 1, 2, 3, 4 and 5 lines respectivly
107  */
108 const int lineOffsets[5][5] = {
109 	{ 82 },
110 	{ 70, 94 },
111 	{ 64, 82, 100 },
112 	{ 60, 75, 89, 104 },
113 	{ 58, 70, 82, 94, 105 },
114 };
115 
116 /**
117  * Maps ASCII character code to font index, as used by the
118  * small, medium and large sized fonts; which corresponds to smaltext.cel,
119  * medtexts.cel and bigtgold.cel respectively.
120  */
121 const BYTE gbFontTransTbl[256] = {
122 	// clang-format off
123 	'\0', 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
124 	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
125 	' ',  '!',  '\"', '#',  '$',  '%',  '&',  '\'', '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
126 	'0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',  '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
127 	'@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',  'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
128 	'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',  'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
129 	'`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',  'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
130 	'p',  'q',  'r',  's',  't',  'u',  'v',  'w',  'x',  'y',  'z',  '{',  '|',  '}',  '~',  0x01,
131 	'C',  'u',  'e',  'a',  'a',  'a',  'a',  'c',  'e',  'e',  'e',  'i',  'i',  'i',  'A',  'A',
132 	'E',  'a',  'A',  'o',  'o',  'o',  'u',  'u',  'y',  'O',  'U',  'c',  'L',  'Y',  'P',  'f',
133 	'a',  'i',  'o',  'u',  'n',  'N',  'a',  'o',  '?',  0x01, 0x01, 0x01, 0x01, '!',  '<',  '>',
134 	'o',  '+',  '2',  '3',  '\'', 'u',  'P',  '.',  ',',  '1',  '0',  '>',  0x01, 0x01, 0x01, '?',
135 	'A',  'A',  'A',  'A',  'A',  'A',  'A',  'C',  'E',  'E',  'E',  'E',  'I',  'I',  'I',  'I',
136 	'D',  'N',  'O',  'O',  'O',  'O',  'O',  'X',  '0',  'U',  'U',  'U',  'U',  'Y',  'b',  'B',
137 	'a',  'a',  'a',  'a',  'a',  'a',  'a',  'c',  'e',  'e',  'e',  'e',  'i',  'i',  'i',  'i',
138 	'o',  'n',  'o',  'o',  'o',  'o',  'o',  '/',  '0',  'u',  'u',  'u',  'u',  'y',  'b',  'y',
139 	// clang-format on
140 };
141 
142 /* data */
143 
144 /** Maps from spell_id to spelicon.cel frame number. */
145 char SpellITbl[] = {
146 	27,
147 	1,
148 	2,
149 	3,
150 	4,
151 	5,
152 	6,
153 	7,
154 	8,
155 	9,
156 	28,
157 	13,
158 	12,
159 	18,
160 	16,
161 	14,
162 	18,
163 	19,
164 	11,
165 	20,
166 	15,
167 	21,
168 	23,
169 	24,
170 	25,
171 	22,
172 	26,
173 	29,
174 	37,
175 	38,
176 	39,
177 	42,
178 	41,
179 	40,
180 	10,
181 	36,
182 	30,
183 	51,
184 	51,
185 	50,
186 	46,
187 	47,
188 	43,
189 	45,
190 	48,
191 	49,
192 	44,
193 	35,
194 	35,
195 	35,
196 	35,
197 	35,
198 };
199 /** Maps from panel_button_id to the position and dimensions of a panel button. */
200 int PanBtnPos[8][5] = {
201 	// clang-format off
202 	{   9,   9, 71, 19, TRUE  }, // char button
203 	{   9,  35, 71, 19, FALSE }, // quests button
204 	{   9,  75, 71, 19, TRUE  }, // map button
205 	{   9, 101, 71, 19, FALSE }, // menu button
206 	{ 560,   9, 71, 19, TRUE  }, // inv button
207 	{ 560,  35, 71, 19, FALSE }, // spells button
208 	{  87,  91, 33, 32, TRUE  }, // chat button
209 	{ 527,  91, 33, 32, TRUE  }, // friendly fire button
210 	// clang-format on
211 };
212 /** Maps from panel_button_id to hotkey name. */
213 const char *const PanBtnHotKey[8] = { "'c'", "'q'", "Tab", "Esc", "'i'", "'b'", "Enter", NULL };
214 /** Maps from panel_button_id to panel button description. */
215 const char *const PanBtnStr[8] = {
216 	"Character Information",
217 	"Quests log",
218 	"Automap",
219 	"Main Menu",
220 	"Inventory",
221 	"Spell book",
222 	"Send Message",
223 	"Player Attack"
224 };
225 /** Maps from attribute_id to the rectangle on screen used for attribute increment buttons. */
226 RECT32 ChrBtnsRect[4] = {
227 	{ 137, 138, 41, 22 },
228 	{ 137, 166, 41, 22 },
229 	{ 137, 195, 41, 22 },
230 	{ 137, 223, 41, 22 }
231 };
232 
233 /** Maps from spellbook page number and position to spell_id. */
234 spell_id SpellPages[6][7] = {
235 	{ SPL_NULL, SPL_FIREBOLT, SPL_CBOLT, SPL_HBOLT, SPL_HEAL, SPL_HEALOTHER, SPL_FLAME },
236 	{ SPL_RESURRECT, SPL_FIREWALL, SPL_TELEKINESIS, SPL_LIGHTNING, SPL_TOWN, SPL_FLASH, SPL_STONE },
237 	{ SPL_RNDTELEPORT, SPL_MANASHIELD, SPL_ELEMENT, SPL_FIREBALL, SPL_WAVE, SPL_CHAIN, SPL_GUARDIAN },
238 	{ SPL_NOVA, SPL_GOLEM, SPL_TELEPORT, SPL_APOCA, SPL_BONESPIRIT, SPL_FLARE, SPL_ETHEREALIZE },
239 	{ SPL_LIGHTWALL, SPL_IMMOLAT, SPL_WARP, SPL_REFLECT, SPL_BERSERK, SPL_FIRERING, SPL_SEARCH },
240 	{ SPL_INVALID, SPL_INVALID, SPL_INVALID, SPL_INVALID, SPL_INVALID, SPL_INVALID, SPL_INVALID }
241 };
242 
243 #define SPLICONLENGTH 56
244 #define SPLROWICONLS 10
245 #define SPLICONLAST (gbIsHellfire ? 52 : 43)
246 
247 /**
248  * Draw spell cell onto the given buffer.
249  * @param out Output buffer
250  * @param xp Buffer coordinate
251  * @param yp Buffer coordinate
252  * @param Trans Pointer to the cel buffer.
253  * @param nCel Index of the cel frame to draw. 0 based.
254  * @param w Width of the frame.
255  */
DrawSpellCel(CelOutputBuffer out,int xp,int yp,BYTE * Trans,int nCel,int w)256 static void DrawSpellCel(CelOutputBuffer out, int xp, int yp, BYTE *Trans, int nCel, int w)
257 {
258 	CelDrawLightTo(out, xp, yp, Trans, nCel, w, SplTransTbl);
259 }
260 
SetSpellTrans(char t)261 void SetSpellTrans(char t)
262 {
263 	int i;
264 
265 	if (t == RSPLTYPE_SKILL) {
266 		for (i = 0; i < 128; i++)
267 			SplTransTbl[i] = i;
268 	}
269 	for (i = 128; i < 256; i++)
270 		SplTransTbl[i] = i;
271 	SplTransTbl[255] = 0;
272 
273 	switch (t) {
274 	case RSPLTYPE_SPELL:
275 		SplTransTbl[PAL8_YELLOW] = PAL16_BLUE + 1;
276 		SplTransTbl[PAL8_YELLOW + 1] = PAL16_BLUE + 3;
277 		SplTransTbl[PAL8_YELLOW + 2] = PAL16_BLUE + 5;
278 		for (i = PAL16_BLUE; i < PAL16_BLUE + 16; i++) {
279 			SplTransTbl[PAL16_BEIGE - PAL16_BLUE + i] = i;
280 			SplTransTbl[PAL16_YELLOW - PAL16_BLUE + i] = i;
281 			SplTransTbl[PAL16_ORANGE - PAL16_BLUE + i] = i;
282 		}
283 		break;
284 	case RSPLTYPE_SCROLL:
285 		SplTransTbl[PAL8_YELLOW] = PAL16_BEIGE + 1;
286 		SplTransTbl[PAL8_YELLOW + 1] = PAL16_BEIGE + 3;
287 		SplTransTbl[PAL8_YELLOW + 2] = PAL16_BEIGE + 5;
288 		for (i = PAL16_BEIGE; i < PAL16_BEIGE + 16; i++) {
289 			SplTransTbl[PAL16_YELLOW - PAL16_BEIGE + i] = i;
290 			SplTransTbl[PAL16_ORANGE - PAL16_BEIGE + i] = i;
291 		}
292 		break;
293 	case RSPLTYPE_CHARGES:
294 		SplTransTbl[PAL8_YELLOW] = PAL16_ORANGE + 1;
295 		SplTransTbl[PAL8_YELLOW + 1] = PAL16_ORANGE + 3;
296 		SplTransTbl[PAL8_YELLOW + 2] = PAL16_ORANGE + 5;
297 		for (i = PAL16_ORANGE; i < PAL16_ORANGE + 16; i++) {
298 			SplTransTbl[PAL16_BEIGE - PAL16_ORANGE + i] = i;
299 			SplTransTbl[PAL16_YELLOW - PAL16_ORANGE + i] = i;
300 		}
301 		break;
302 	case RSPLTYPE_INVALID:
303 		SplTransTbl[PAL8_YELLOW] = PAL16_GRAY + 1;
304 		SplTransTbl[PAL8_YELLOW + 1] = PAL16_GRAY + 3;
305 		SplTransTbl[PAL8_YELLOW + 2] = PAL16_GRAY + 5;
306 		for (i = PAL16_GRAY; i < PAL16_GRAY + 15; i++) {
307 			SplTransTbl[PAL16_BEIGE - PAL16_GRAY + i] = i;
308 			SplTransTbl[PAL16_YELLOW - PAL16_GRAY + i] = i;
309 			SplTransTbl[PAL16_ORANGE - PAL16_GRAY + i] = i;
310 		}
311 		SplTransTbl[PAL16_BEIGE + 15] = 0;
312 		SplTransTbl[PAL16_YELLOW + 15] = 0;
313 		SplTransTbl[PAL16_ORANGE + 15] = 0;
314 		break;
315 	}
316 }
317 
318 /**
319  * Sets the spell frame to draw and its position then draws it.
320  */
DrawSpell(CelOutputBuffer out)321 static void DrawSpell(CelOutputBuffer out)
322 {
323 	char st;
324 	int spl, tlvl;
325 
326 	spl = plr[myplr]._pRSpell;
327 	st = plr[myplr]._pRSplType;
328 
329 	// BUGFIX: Move the next line into the if statement to avoid OOB (SPL_INVALID is -1) (fixed)
330 	if (st == RSPLTYPE_SPELL && spl != SPL_INVALID) {
331 		tlvl = plr[myplr]._pISplLvlAdd + plr[myplr]._pSplLvl[spl];
332 		if (!CheckSpell(myplr, spl, RSPLTYPE_SPELL, TRUE))
333 			st = RSPLTYPE_INVALID;
334 		if (tlvl <= 0)
335 			st = RSPLTYPE_INVALID;
336 	}
337 	if (currlevel == 0 && st != RSPLTYPE_INVALID && !spelldata[spl].sTownSpell)
338 		st = RSPLTYPE_INVALID;
339 	if (plr[myplr]._pRSpell < 0)
340 		st = RSPLTYPE_INVALID;
341 	SetSpellTrans(st);
342 	if (spl != SPL_INVALID)
343 		DrawSpellCel(out, PANEL_X + 565, PANEL_Y + 119, pSpellCels, SpellITbl[spl], SPLICONLENGTH);
344 	else
345 		DrawSpellCel(out, PANEL_X + 565, PANEL_Y + 119, pSpellCels, 27, SPLICONLENGTH);
346 }
347 
DrawSpellList(CelOutputBuffer out)348 void DrawSpellList(CelOutputBuffer out)
349 {
350 	int x, y, c, s, t, v, lx, ly, trans;
351 	Uint64 mask, spl;
352 
353 	pSpell = SPL_INVALID;
354 	infostr[0] = '\0';
355 	x = PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS;
356 	y = PANEL_Y - 17;
357 	ClearPanel();
358 
359 	for (Sint32 i = RSPLTYPE_SKILL; i < RSPLTYPE_INVALID; i++) {
360 		switch ((spell_type)i) {
361 		case RSPLTYPE_SKILL:
362 			SetSpellTrans(RSPLTYPE_SKILL);
363 			mask = plr[myplr]._pAblSpells;
364 			c = SPLICONLAST + 3;
365 			break;
366 		case RSPLTYPE_SPELL:
367 			mask = plr[myplr]._pMemSpells;
368 			c = SPLICONLAST + 4;
369 			break;
370 		case RSPLTYPE_SCROLL:
371 			SetSpellTrans(RSPLTYPE_SCROLL);
372 			mask = plr[myplr]._pScrlSpells;
373 			c = SPLICONLAST + 1;
374 			break;
375 		case RSPLTYPE_CHARGES:
376 			SetSpellTrans(RSPLTYPE_CHARGES);
377 			mask = plr[myplr]._pISpells;
378 			c = SPLICONLAST + 2;
379 			break;
380 		case RSPLTYPE_INVALID:
381 			break;
382 		}
383 		Sint32 j = SPL_FIREBOLT;
384 		for (spl = 1; j < MAX_SPELLS; spl <<= 1, j++) {
385 			if (!(mask & spl))
386 				continue;
387 			if (i == RSPLTYPE_SPELL) {
388 				s = plr[myplr]._pISplLvlAdd + plr[myplr]._pSplLvl[j];
389 				if (s < 0)
390 					s = 0;
391 				if (s > 0)
392 					trans = RSPLTYPE_SPELL;
393 				else
394 					trans = RSPLTYPE_INVALID;
395 				SetSpellTrans(trans);
396 			}
397 			if (currlevel == 0 && !spelldata[j].sTownSpell)
398 				SetSpellTrans(RSPLTYPE_INVALID);
399 			DrawSpellCel(out, x, y, pSpellCels, SpellITbl[j], SPLICONLENGTH);
400 			lx = x;
401 			ly = y - SPLICONLENGTH;
402 			if (MouseX >= lx && MouseX < lx + SPLICONLENGTH && MouseY >= ly && MouseY < ly + SPLICONLENGTH) {
403 				pSpell = (spell_id)j;
404 				pSplType = (spell_type)i;
405 				if (plr[myplr]._pClass == PC_MONK && j == SPL_SEARCH)
406 					pSplType = RSPLTYPE_SKILL;
407 				DrawSpellCel(out, x, y, pSpellCels, c, SPLICONLENGTH);
408 				switch (pSplType) {
409 				case RSPLTYPE_SKILL:
410 					sprintf(infostr, "%s Skill", spelldata[pSpell].sSkillText);
411 					break;
412 				case RSPLTYPE_SPELL:
413 					sprintf(infostr, "%s Spell", spelldata[pSpell].sNameText);
414 					if (pSpell == SPL_HBOLT) {
415 						sprintf(tempstr, "Damages undead only");
416 						AddPanelString(tempstr, TRUE);
417 					}
418 					if (s == 0)
419 						sprintf(tempstr, "Spell Level 0 - Unusable");
420 					else
421 						sprintf(tempstr, "Spell Level %i", s);
422 					AddPanelString(tempstr, TRUE);
423 					break;
424 				case RSPLTYPE_SCROLL:
425 					sprintf(infostr, "Scroll of %s", spelldata[pSpell].sNameText);
426 					v = 0;
427 					for (t = 0; t < plr[myplr]._pNumInv; t++) {
428 						if (!plr[myplr].InvList[t].isEmpty()
429 						    && (plr[myplr].InvList[t]._iMiscId == IMISC_SCROLL || plr[myplr].InvList[t]._iMiscId == IMISC_SCROLLT)
430 						    && plr[myplr].InvList[t]._iSpell == pSpell) {
431 							v++;
432 						}
433 					}
434 					for (t = 0; t < MAXBELTITEMS; t++) {
435 						if (!plr[myplr].SpdList[t].isEmpty()
436 						    && (plr[myplr].SpdList[t]._iMiscId == IMISC_SCROLL || plr[myplr].SpdList[t]._iMiscId == IMISC_SCROLLT)
437 						    && plr[myplr].SpdList[t]._iSpell == pSpell) {
438 							v++;
439 						}
440 					}
441 					if (v == 1)
442 						strcpy(tempstr, "1 Scroll");
443 					else
444 						sprintf(tempstr, "%i Scrolls", v);
445 					AddPanelString(tempstr, TRUE);
446 					break;
447 				case RSPLTYPE_CHARGES:
448 					sprintf(infostr, "Staff of %s", spelldata[pSpell].sNameText);
449 					if (plr[myplr].InvBody[INVLOC_HAND_LEFT]._iCharges == 1)
450 						strcpy(tempstr, "1 Charge");
451 					else
452 						sprintf(tempstr, "%i Charges", plr[myplr].InvBody[INVLOC_HAND_LEFT]._iCharges);
453 					AddPanelString(tempstr, TRUE);
454 					break;
455 				case RSPLTYPE_INVALID:
456 					break;
457 				}
458 				for (t = 0; t < 4; t++) {
459 					if (plr[myplr]._pSplHotKey[t] == pSpell && plr[myplr]._pSplTHotKey[t] == pSplType) {
460 						DrawSpellCel(out, x, y, pSpellCels, t + SPLICONLAST + 5, SPLICONLENGTH);
461 						sprintf(tempstr, "Spell Hotkey #F%i", t + 5);
462 						AddPanelString(tempstr, TRUE);
463 					}
464 				}
465 			}
466 			x -= SPLICONLENGTH;
467 			if (x == PANEL_X + 12 - SPLICONLENGTH) {
468 				x = PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS;
469 				y -= SPLICONLENGTH;
470 			}
471 		}
472 		if (mask != 0 && x != PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS)
473 			x -= SPLICONLENGTH;
474 		if (x == PANEL_X + 12 - SPLICONLENGTH) {
475 			x = PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS;
476 			y -= SPLICONLENGTH;
477 		}
478 	}
479 }
480 
SetSpell()481 void SetSpell()
482 {
483 	spselflag = FALSE;
484 	if (pSpell != SPL_INVALID) {
485 		ClearPanel();
486 		plr[myplr]._pRSpell = pSpell;
487 		plr[myplr]._pRSplType = pSplType;
488 		force_redraw = 255;
489 	}
490 }
491 
SetSpeedSpell(int slot)492 void SetSpeedSpell(int slot)
493 {
494 	if (pSpell != SPL_INVALID) {
495 		for (int i = 0; i < 4; ++i) {
496 			if (plr[myplr]._pSplHotKey[i] == pSpell && plr[myplr]._pSplTHotKey[i] == pSplType)
497 				plr[myplr]._pSplHotKey[i] = SPL_INVALID;
498 		}
499 		plr[myplr]._pSplHotKey[slot] = pSpell;
500 		plr[myplr]._pSplTHotKey[slot] = pSplType;
501 	}
502 }
503 
ToggleSpell(int slot)504 void ToggleSpell(int slot)
505 {
506 	Uint64 spells;
507 
508 	if (plr[myplr]._pSplHotKey[slot] == SPL_INVALID) {
509 		return;
510 	}
511 
512 	switch (plr[myplr]._pSplTHotKey[slot]) {
513 	case RSPLTYPE_SKILL:
514 		spells = plr[myplr]._pAblSpells;
515 		break;
516 	case RSPLTYPE_SPELL:
517 		spells = plr[myplr]._pMemSpells;
518 		break;
519 	case RSPLTYPE_SCROLL:
520 		spells = plr[myplr]._pScrlSpells;
521 		break;
522 	case RSPLTYPE_CHARGES:
523 		spells = plr[myplr]._pISpells;
524 		break;
525 	case RSPLTYPE_INVALID:
526 		return;
527 	}
528 
529 	if (spells & GetSpellBitmask(plr[myplr]._pSplHotKey[slot])) {
530 		plr[myplr]._pRSpell = plr[myplr]._pSplHotKey[slot];
531 		plr[myplr]._pRSplType = plr[myplr]._pSplTHotKey[slot];
532 		force_redraw = 255;
533 	}
534 }
535 
PrintChar(CelOutputBuffer out,int sx,int sy,int nCel,text_color col)536 void PrintChar(CelOutputBuffer out, int sx, int sy, int nCel, text_color col)
537 {
538 	int i;
539 	BYTE pix;
540 	BYTE tbl[256];
541 
542 	switch (col) {
543 	case COL_WHITE:
544 		CelDrawTo(out, sx, sy, pPanelText, nCel, 13);
545 		return;
546 	case COL_BLUE:
547 		for (i = 0; i < 256; i++) {
548 			pix = i;
549 			if (pix > PAL16_GRAY + 13)
550 				pix = PAL16_BLUE + 15;
551 			else if (pix >= PAL16_GRAY)
552 				pix -= PAL16_GRAY - (PAL16_BLUE + 2);
553 			tbl[i] = pix;
554 		}
555 		break;
556 	case COL_RED:
557 		for (i = 0; i < 256; i++) {
558 			pix = i;
559 			if (pix >= PAL16_GRAY)
560 				pix -= PAL16_GRAY - PAL16_RED;
561 			tbl[i] = pix;
562 		}
563 		break;
564 	case COL_GOLD:
565 		for (i = 0; i < 256; i++) {
566 			pix = i;
567 			if (pix >= PAL16_GRAY) {
568 				if (pix >= PAL16_GRAY + 14)
569 					pix = PAL16_YELLOW + 15;
570 				else
571 					pix -= PAL16_GRAY - (PAL16_YELLOW + 2);
572 			}
573 			tbl[i] = pix;
574 		}
575 		break;
576 	case COL_BLACK:
577 		light_table_index = 15;
578 		CelDrawLightTo(out, sx, sy, pPanelText, nCel, 13, NULL);
579 		return;
580 	}
581 	CelDrawLightTo(out, sx, sy, pPanelText, nCel, 13, tbl);
582 }
583 
AddPanelString(const char * str,BOOL just)584 void AddPanelString(const char *str, BOOL just)
585 {
586 	strcpy(panelstr[pnumlines], str);
587 	pstrjust[pnumlines] = just;
588 
589 	if (pnumlines < 4)
590 		pnumlines++;
591 }
592 
ClearPanel()593 void ClearPanel()
594 {
595 	pnumlines = 0;
596 	pinfoflag = FALSE;
597 }
598 
DrawPanelBox(CelOutputBuffer out,int x,int y,int w,int h,int sx,int sy)599 void DrawPanelBox(CelOutputBuffer out, int x, int y, int w, int h, int sx, int sy)
600 {
601 	const BYTE *src = pBtmBuff.at(x, y);
602 	BYTE *dst = out.at(sx, sy);
603 
604 	for (int hgt = h; hgt; hgt--, src += pBtmBuff.pitch(), dst += out.pitch()) {
605 		memcpy(dst, src, w);
606 	}
607 }
608 
609 /**
610  * Draws a section of the empty flask cel on top of the panel to create the illusion
611  * of the flask getting empty. This function takes a cel and draws a
612  * horizontal stripe of height (max-min) onto the given buffer.
613  * @param out Target buffer.
614  * @param sx Buffer coordinate
615  * @param sy Buffer coordinate
616  * @param celBuf Buffer of the empty flask cel.
617  * @param y0 Top of the flask cel section to draw.
618  * @param y1 Bottom of the flask cel section to draw.
619  */
DrawFlaskTop(CelOutputBuffer out,int sx,int sy,CelOutputBuffer celBuf,int y0,int y1)620 static void DrawFlaskTop(CelOutputBuffer out, int sx, int sy, CelOutputBuffer celBuf, int y0, int y1)
621 {
622 	const BYTE *src = celBuf.at(0, y0);
623 	BYTE *dst = out.at(sx, sy);
624 
625 	for (int h = y1 - y0; h != 0; --h, src += celBuf.pitch(), dst += out.pitch())
626 		memcpy(dst, src, celBuf.w());
627 }
628 
629 /**
630  * Draws the dome of the flask that protrudes above the panel top line.
631  * It draws a rectangle of fixed width 59 and height 'h' from the source buffer
632  * into the target buffer.
633  * @param out The target buffer.
634  * @param celBuf Buffer of the empty flask cel.
635  * @param celX Source buffer start coordinate.
636  * @param celY Source buffer start coordinate.
637  * @param sx Target buffer coordinate.
638  * @param sy Target buffer coordinate.
639  * @param h How many lines of the source buffer that will be copied.
640  */
DrawFlask(CelOutputBuffer out,CelOutputBuffer celBuf,int celX,int celY,int x,int y,int h)641 static void DrawFlask(CelOutputBuffer out, CelOutputBuffer celBuf, int celX, int celY, int x, int y, int h)
642 {
643 	int wdt, hgt;
644 	const BYTE *src = celBuf.at(celX, celY);
645 	BYTE *dst = out.at(x, y);
646 
647 	for (hgt = h; hgt; hgt--, src += celBuf.pitch() - 59, dst += out.pitch() - 59) {
648 		for (wdt = 59; wdt; wdt--) {
649 			if (*src)
650 				*dst = *src;
651 			src++;
652 			dst++;
653 		}
654 	}
655 }
656 
DrawLifeFlask(CelOutputBuffer out)657 void DrawLifeFlask(CelOutputBuffer out)
658 {
659 	double p;
660 	int filled;
661 
662 	p = 0.0;
663 	if (plr[myplr]._pMaxHP > 0) {
664 		p = (double)plr[myplr]._pHitPoints / (double)plr[myplr]._pMaxHP * 80.0;
665 	}
666 	plr[myplr]._pHPPer = p;
667 	filled = plr[myplr]._pHPPer;
668 
669 	if (filled > 80)
670 		filled = 80;
671 
672 	filled = 80 - filled;
673 	if (filled > 11)
674 		filled = 11;
675 	filled += 2;
676 
677 	DrawFlask(out, pLifeBuff, 13, 3, PANEL_LEFT + 109, PANEL_TOP - 13, filled);
678 	if (filled != 13)
679 		DrawFlask(out, pBtmBuff, 109, filled + 3, PANEL_LEFT + 109, PANEL_TOP - 13 + filled, 13 - filled);
680 }
681 
UpdateLifeFlask(CelOutputBuffer out)682 void UpdateLifeFlask(CelOutputBuffer out)
683 {
684 	double p;
685 	int filled;
686 
687 	p = 0.0;
688 	if (plr[myplr]._pMaxHP > 0) {
689 		p = (double)plr[myplr]._pHitPoints / (double)plr[myplr]._pMaxHP * 80.0;
690 	}
691 	filled = p;
692 	plr[myplr]._pHPPer = filled;
693 
694 	if (filled > 69)
695 		filled = 69;
696 	else if (filled < 0)
697 		filled = 0;
698 	if (filled != 69)
699 		DrawFlaskTop(out, 96 + PANEL_X, PANEL_Y, pLifeBuff, 16, 85 - filled);
700 	if (filled != 0)
701 		DrawPanelBox(out, 96, 85 - filled, 88, filled, 96 + PANEL_X, PANEL_Y + 69 - filled);
702 }
703 
DrawManaFlask(CelOutputBuffer out)704 void DrawManaFlask(CelOutputBuffer out)
705 {
706 	int filled = plr[myplr]._pManaPer;
707 	if (filled > 80)
708 		filled = 80;
709 	filled = 80 - filled;
710 	if (filled > 11)
711 		filled = 11;
712 	filled += 2;
713 
714 	DrawFlask(out, pManaBuff, 13, 3, PANEL_LEFT + 475, PANEL_TOP - 13, filled);
715 	if (filled != 13)
716 		DrawFlask(out, pBtmBuff, 475, filled + 3, PANEL_LEFT + 475, PANEL_TOP - 13 + filled, 13 - filled);
717 }
718 
control_update_life_mana()719 void control_update_life_mana()
720 {
721 	int manaPer;
722 	int maxMana = plr[myplr]._pMaxMana;
723 	int mana = plr[myplr]._pMana;
724 	if (maxMana < 0)
725 		maxMana = 0;
726 	if (mana < 0)
727 		mana = 0;
728 	if (maxMana == 0)
729 		manaPer = 0;
730 	else
731 		manaPer = (double)mana / (double)maxMana * 80.0;
732 	plr[myplr]._pManaPer = manaPer;
733 	plr[myplr]._pHPPer = (double)plr[myplr]._pHitPoints / (double)plr[myplr]._pMaxHP * 80.0;
734 }
735 
UpdateManaFlask(CelOutputBuffer out)736 void UpdateManaFlask(CelOutputBuffer out)
737 {
738 	int filled;
739 	int maxMana = plr[myplr]._pMaxMana;
740 	int mana = plr[myplr]._pMana;
741 	if (maxMana < 0)
742 		maxMana = 0;
743 	if (mana < 0)
744 		mana = 0;
745 
746 	if (maxMana == 0)
747 		filled = 0;
748 	else
749 		filled = (double)mana / (double)maxMana * 80.0;
750 
751 	plr[myplr]._pManaPer = filled;
752 
753 	if (filled > 69)
754 		filled = 69;
755 	if (filled != 69)
756 		DrawFlaskTop(out, PANEL_X + 464, PANEL_Y, pManaBuff, 16, 85 - filled);
757 	if (filled != 0)
758 		DrawPanelBox(out, 464, 85 - filled, 88, filled, PANEL_X + 464, PANEL_Y + 69 - filled);
759 
760 	DrawSpell(out);
761 }
762 
InitControlPan()763 void InitControlPan()
764 {
765 	int i;
766 	pBtmBuff = CelOutputBuffer::Alloc(PANEL_WIDTH, (PANEL_HEIGHT + 16) * (gbIsMultiplayer ? 2 : 1));
767 	pManaBuff = CelOutputBuffer::Alloc(88, 88);
768 	pLifeBuff = CelOutputBuffer::Alloc(88, 88);
769 
770 	pPanelText = LoadFileInMem("CtrlPan\\SmalText.CEL", NULL);
771 	pChrPanel = LoadFileInMem("Data\\Char.CEL", NULL);
772 	if (!gbIsHellfire)
773 		pSpellCels = LoadFileInMem("CtrlPan\\SpelIcon.CEL", NULL);
774 	else
775 		pSpellCels = LoadFileInMem("Data\\SpelIcon.CEL", NULL);
776 	SetSpellTrans(RSPLTYPE_SKILL);
777 	BYTE *pStatusPanel = LoadFileInMem("CtrlPan\\Panel8.CEL", NULL);
778 	CelDrawUnsafeTo(pBtmBuff, 0, (PANEL_HEIGHT + 16) - 1, pStatusPanel, 1, PANEL_WIDTH);
779 	MemFreeDbg(pStatusPanel);
780 	pStatusPanel = LoadFileInMem("CtrlPan\\P8Bulbs.CEL", NULL);
781 	CelDrawUnsafeTo(pLifeBuff, 0, 87, pStatusPanel, 1, 88);
782 	CelDrawUnsafeTo(pManaBuff, 0, 87, pStatusPanel, 2, 88);
783 	MemFreeDbg(pStatusPanel);
784 	talkflag = FALSE;
785 	if (gbIsMultiplayer) {
786 		BYTE *pTalkPanel = LoadFileInMem("CtrlPan\\TalkPanl.CEL", NULL);
787 		CelDrawUnsafeTo(pBtmBuff, 0, (PANEL_HEIGHT + 16) * 2 - 1, pTalkPanel, 1, PANEL_WIDTH);
788 		MemFreeDbg(pTalkPanel);
789 		pMultiBtns = LoadFileInMem("CtrlPan\\P8But2.CEL", NULL);
790 		pTalkBtns = LoadFileInMem("CtrlPan\\TalkButt.CEL", NULL);
791 		sgbPlrTalkTbl = 0;
792 		sgszTalkMsg[0] = '\0';
793 		for (i = 0; i < MAX_PLRS; i++)
794 			whisper[i] = TRUE;
795 		for (i = 0; i < sizeof(talkbtndown) / sizeof(talkbtndown[0]); i++)
796 			talkbtndown[i] = FALSE;
797 	}
798 	panelflag = FALSE;
799 	lvlbtndown = FALSE;
800 	pPanelButtons = LoadFileInMem("CtrlPan\\Panel8bu.CEL", NULL);
801 	for (i = 0; i < sizeof(panbtn) / sizeof(panbtn[0]); i++)
802 		panbtn[i] = FALSE;
803 	panbtndown = FALSE;
804 	if (!gbIsMultiplayer)
805 		numpanbtns = 6;
806 	else
807 		numpanbtns = 8;
808 	pChrButtons = LoadFileInMem("Data\\CharBut.CEL", NULL);
809 	for (i = 0; i < sizeof(chrbtn) / sizeof(chrbtn[0]); i++)
810 		chrbtn[i] = FALSE;
811 	chrbtnactive = FALSE;
812 	pDurIcons = LoadFileInMem("Items\\DurIcons.CEL", NULL);
813 	strcpy(infostr, "");
814 	ClearPanel();
815 	drawhpflag = TRUE;
816 	drawmanaflag = TRUE;
817 	chrflag = FALSE;
818 	spselflag = FALSE;
819 	pSpellBkCel = LoadFileInMem("Data\\SpellBk.CEL", NULL);
820 	pSBkBtnCel = LoadFileInMem("Data\\SpellBkB.CEL", NULL);
821 	pSBkIconCels = LoadFileInMem("Data\\SpellI2.CEL", NULL);
822 	sbooktab = 0;
823 	sbookflag = FALSE;
824 	if (plr[myplr]._pClass == PC_WARRIOR) {
825 		SpellPages[0][0] = SPL_REPAIR;
826 	} else if (plr[myplr]._pClass == PC_ROGUE) {
827 		SpellPages[0][0] = SPL_DISARM;
828 	} else if (plr[myplr]._pClass == PC_SORCERER) {
829 		SpellPages[0][0] = SPL_RECHARGE;
830 	} else if (plr[myplr]._pClass == PC_MONK) {
831 		SpellPages[0][0] = SPL_SEARCH;
832 	} else if (plr[myplr]._pClass == PC_BARD) {
833 		SpellPages[0][0] = SPL_IDENTIFY;
834 	} else if (plr[myplr]._pClass == PC_BARBARIAN) {
835 		SpellPages[0][0] = SPL_BLODBOIL;
836 	}
837 	pQLogCel = LoadFileInMem("Data\\Quest.CEL", NULL);
838 	pGBoxBuff = LoadFileInMem("CtrlPan\\Golddrop.cel", NULL);
839 	dropGoldFlag = FALSE;
840 	dropGoldValue = 0;
841 	initialDropGoldValue = 0;
842 	initialDropGoldIndex = 0;
843 }
844 
DrawCtrlPan(CelOutputBuffer out)845 void DrawCtrlPan(CelOutputBuffer out)
846 {
847 	DrawPanelBox(out, 0, sgbPlrTalkTbl + 16, PANEL_WIDTH, PANEL_HEIGHT, PANEL_X, PANEL_Y);
848 	DrawInfoBox(out);
849 }
850 
DrawCtrlBtns(CelOutputBuffer out)851 void DrawCtrlBtns(CelOutputBuffer out)
852 {
853 	int i;
854 
855 	for (i = 0; i < 6; i++) {
856 		if (!panbtn[i])
857 			DrawPanelBox(out, PanBtnPos[i][0], PanBtnPos[i][1] + 16, 71, 20, PanBtnPos[i][0] + PANEL_X, PanBtnPos[i][1] + PANEL_Y);
858 		else
859 			CelDrawTo(out, PanBtnPos[i][0] + PANEL_X, PanBtnPos[i][1] + PANEL_Y + 18, pPanelButtons, i + 1, 71);
860 	}
861 	if (numpanbtns == 8) {
862 		CelDrawTo(out, 87 + PANEL_X, 122 + PANEL_Y, pMultiBtns, panbtn[6] + 1, 33);
863 		if (gbFriendlyMode)
864 			CelDrawTo(out, 527 + PANEL_X, 122 + PANEL_Y, pMultiBtns, panbtn[7] + 3, 33);
865 		else
866 			CelDrawTo(out, 527 + PANEL_X, 122 + PANEL_Y, pMultiBtns, panbtn[7] + 5, 33);
867 	}
868 }
869 
870 /**
871  * Draws the "Speed Book": the rows of known spells for quick-setting a spell that
872  * show up when you click the spell slot at the control panel.
873  */
DoSpeedBook()874 void DoSpeedBook()
875 {
876 	int xo, yo, X, Y, i, j;
877 
878 	spselflag = TRUE;
879 	xo = PANEL_X + 12 + SPLICONLENGTH * 10;
880 	yo = PANEL_Y - 17;
881 	X = xo + SPLICONLENGTH / 2;
882 	Y = yo - SPLICONLENGTH / 2;
883 
884 	if (plr[myplr]._pRSpell != SPL_INVALID) {
885 		for (i = 0; i < 4; i++) {
886 			Uint64 spells;
887 			switch (i) {
888 			case RSPLTYPE_SKILL:
889 				spells = plr[myplr]._pAblSpells;
890 				break;
891 			case RSPLTYPE_SPELL:
892 				spells = plr[myplr]._pMemSpells;
893 				break;
894 			case RSPLTYPE_SCROLL:
895 				spells = plr[myplr]._pScrlSpells;
896 				break;
897 			case RSPLTYPE_CHARGES:
898 				spells = plr[myplr]._pISpells;
899 				break;
900 			}
901 			Uint64 spell = 1;
902 			for (j = 1; j < MAX_SPELLS; j++) {
903 				if (spell & spells) {
904 					if (j == plr[myplr]._pRSpell && i == plr[myplr]._pRSplType) {
905 						X = xo + SPLICONLENGTH / 2;
906 						Y = yo - SPLICONLENGTH / 2;
907 					}
908 					xo -= SPLICONLENGTH;
909 					if (xo == PANEL_X + 12 - SPLICONLENGTH) {
910 						xo = PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS;
911 						yo -= SPLICONLENGTH;
912 					}
913 				}
914 				spell <<= 1ULL;
915 			}
916 			if (spells && xo != PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS)
917 				xo -= SPLICONLENGTH;
918 			if (xo == PANEL_X + 12 - SPLICONLENGTH) {
919 				xo = PANEL_X + 12 + SPLICONLENGTH * SPLROWICONLS;
920 				yo -= SPLICONLENGTH;
921 			}
922 		}
923 	}
924 
925 	SetCursorPos(X, Y);
926 }
927 
928 /**
929  * Checks if the mouse cursor is within any of the panel buttons and flag it if so.
930  */
DoPanBtn()931 void DoPanBtn()
932 {
933 	int i;
934 
935 	for (i = 0; i < numpanbtns; i++) {
936 		int x = PanBtnPos[i][0] + PANEL_LEFT + PanBtnPos[i][2];
937 		int y = PanBtnPos[i][1] + PANEL_TOP + PanBtnPos[i][3];
938 		if (MouseX >= PanBtnPos[i][0] + PANEL_LEFT && MouseX <= x) {
939 			if (MouseY >= PanBtnPos[i][1] + PANEL_TOP && MouseY <= y) {
940 				panbtn[i] = TRUE;
941 				drawbtnflag = TRUE;
942 				panbtndown = TRUE;
943 			}
944 		}
945 	}
946 	if (!spselflag && MouseX >= 565 + PANEL_LEFT && MouseX < 621 + PANEL_LEFT && MouseY >= 64 + PANEL_TOP && MouseY < 120 + PANEL_TOP) {
947 		if (SDL_GetModState() & KMOD_SHIFT) {
948 			plr[myplr]._pRSpell = SPL_INVALID;
949 			plr[myplr]._pRSplType = RSPLTYPE_INVALID;
950 			force_redraw = 255;
951 			return;
952 		}
953 		DoSpeedBook();
954 		gamemenu_off();
955 	}
956 }
957 
control_set_button_down(int btn_id)958 void control_set_button_down(int btn_id)
959 {
960 	panbtn[btn_id] = TRUE;
961 	drawbtnflag = TRUE;
962 	panbtndown = TRUE;
963 }
964 
control_check_btn_press()965 void control_check_btn_press()
966 {
967 	int x, y;
968 
969 	x = PanBtnPos[3][0] + PANEL_LEFT + PanBtnPos[3][2];
970 	y = PanBtnPos[3][1] + PANEL_TOP + PanBtnPos[3][3];
971 	if (MouseX >= PanBtnPos[3][0] + PANEL_LEFT
972 	    && MouseX <= x
973 	    && MouseY >= PanBtnPos[3][1] + PANEL_TOP
974 	    && MouseY <= y) {
975 		control_set_button_down(3);
976 	}
977 	x = PanBtnPos[6][0] + PANEL_LEFT + PanBtnPos[6][2];
978 	y = PanBtnPos[6][1] + PANEL_TOP + PanBtnPos[6][3];
979 	if (MouseX >= PanBtnPos[6][0] + PANEL_LEFT
980 	    && MouseX <= x
981 	    && MouseY >= PanBtnPos[6][1] + PANEL_TOP
982 	    && MouseY <= y) {
983 		control_set_button_down(6);
984 	}
985 }
986 
DoAutoMap()987 void DoAutoMap()
988 {
989 	if (currlevel != 0 || gbIsMultiplayer) {
990 		if (!automapflag)
991 			StartAutomap();
992 		else
993 			automapflag = FALSE;
994 	} else {
995 		InitDiabloMsg(EMSG_NO_AUTOMAP_IN_TOWN);
996 	}
997 }
998 
999 /**
1000  * Checks the mouse cursor position within the control panel and sets information
1001  * strings if needed.
1002  */
CheckPanelInfo()1003 void CheckPanelInfo()
1004 {
1005 	int i, c, s, xend, yend;
1006 
1007 	panelflag = FALSE;
1008 	ClearPanel();
1009 	for (i = 0; i < numpanbtns; i++) {
1010 		xend = PanBtnPos[i][0] + PANEL_LEFT + PanBtnPos[i][2];
1011 		yend = PanBtnPos[i][1] + PANEL_TOP + PanBtnPos[i][3];
1012 		if (MouseX >= PanBtnPos[i][0] + PANEL_LEFT && MouseX <= xend && MouseY >= PanBtnPos[i][1] + PANEL_TOP && MouseY <= yend) {
1013 			if (i != 7) {
1014 				strcpy(infostr, PanBtnStr[i]);
1015 			} else {
1016 				if (gbFriendlyMode)
1017 					strcpy(infostr, "Player friendly");
1018 				else
1019 					strcpy(infostr, "Player attack");
1020 			}
1021 			if (PanBtnHotKey[i] != NULL) {
1022 				sprintf(tempstr, "Hotkey: %s", PanBtnHotKey[i]);
1023 				AddPanelString(tempstr, TRUE);
1024 			}
1025 			infoclr = COL_WHITE;
1026 			panelflag = TRUE;
1027 			pinfoflag = TRUE;
1028 		}
1029 	}
1030 	if (!spselflag && MouseX >= 565 + PANEL_LEFT && MouseX < 621 + PANEL_LEFT && MouseY >= 64 + PANEL_TOP && MouseY < 120 + PANEL_TOP) {
1031 		strcpy(infostr, "Select current spell button");
1032 		infoclr = COL_WHITE;
1033 		panelflag = TRUE;
1034 		pinfoflag = TRUE;
1035 		strcpy(tempstr, "Hotkey: 's'");
1036 		AddPanelString(tempstr, TRUE);
1037 		spell_id v = plr[myplr]._pRSpell;
1038 		if (v != SPL_INVALID) {
1039 			switch (plr[myplr]._pRSplType) {
1040 			case RSPLTYPE_SKILL:
1041 				sprintf(tempstr, "%s Skill", spelldata[v].sSkillText);
1042 				AddPanelString(tempstr, TRUE);
1043 				break;
1044 			case RSPLTYPE_SPELL:
1045 				sprintf(tempstr, "%s Spell", spelldata[v].sNameText);
1046 				AddPanelString(tempstr, TRUE);
1047 				c = plr[myplr]._pISplLvlAdd + plr[myplr]._pSplLvl[v];
1048 				if (c < 0)
1049 					c = 0;
1050 				if (c == 0)
1051 					sprintf(tempstr, "Spell Level 0 - Unusable");
1052 				else
1053 					sprintf(tempstr, "Spell Level %i", c);
1054 				AddPanelString(tempstr, TRUE);
1055 				break;
1056 			case RSPLTYPE_SCROLL:
1057 				sprintf(tempstr, "Scroll of %s", spelldata[v].sNameText);
1058 				AddPanelString(tempstr, TRUE);
1059 				s = 0;
1060 				for (i = 0; i < plr[myplr]._pNumInv; i++) {
1061 					if (!plr[myplr].InvList[i].isEmpty()
1062 					    && (plr[myplr].InvList[i]._iMiscId == IMISC_SCROLL || plr[myplr].InvList[i]._iMiscId == IMISC_SCROLLT)
1063 					    && plr[myplr].InvList[i]._iSpell == v) {
1064 						s++;
1065 					}
1066 				}
1067 				for (i = 0; i < MAXBELTITEMS; i++) {
1068 					if (!plr[myplr].SpdList[i].isEmpty()
1069 					    && (plr[myplr].SpdList[i]._iMiscId == IMISC_SCROLL || plr[myplr].SpdList[i]._iMiscId == IMISC_SCROLLT)
1070 					    && plr[myplr].SpdList[i]._iSpell == v) {
1071 						s++;
1072 					}
1073 				}
1074 				if (s == 1)
1075 					strcpy(tempstr, "1 Scroll");
1076 				else
1077 					sprintf(tempstr, "%i Scrolls", s);
1078 				AddPanelString(tempstr, TRUE);
1079 				break;
1080 			case RSPLTYPE_CHARGES:
1081 				sprintf(tempstr, "Staff of %s", spelldata[v].sNameText);
1082 				AddPanelString(tempstr, TRUE);
1083 				if (plr[myplr].InvBody[INVLOC_HAND_LEFT]._iCharges == 1)
1084 					strcpy(tempstr, "1 Charge");
1085 				else
1086 					sprintf(tempstr, "%i Charges", plr[myplr].InvBody[INVLOC_HAND_LEFT]._iCharges);
1087 				AddPanelString(tempstr, TRUE);
1088 				break;
1089 			case RSPLTYPE_INVALID:
1090 				break;
1091 			}
1092 		}
1093 	}
1094 	if (MouseX > 190 + PANEL_LEFT && MouseX < 437 + PANEL_LEFT && MouseY > 4 + PANEL_TOP && MouseY < 33 + PANEL_TOP)
1095 		pcursinvitem = CheckInvHLight();
1096 }
1097 
1098 /**
1099  * Check if the mouse is within a control panel button that's flagged.
1100  * Takes apropiate action if so.
1101  */
CheckBtnUp()1102 void CheckBtnUp()
1103 {
1104 	int i;
1105 	BOOLEAN gamemenuOff;
1106 
1107 	gamemenuOff = TRUE;
1108 	drawbtnflag = TRUE;
1109 	panbtndown = FALSE;
1110 
1111 	for (i = 0; i < 8; i++) {
1112 		if (!panbtn[i]) {
1113 			continue;
1114 		}
1115 
1116 		panbtn[i] = FALSE;
1117 
1118 		if (MouseX < PanBtnPos[i][0] + PANEL_LEFT
1119 		    || MouseX > PanBtnPos[i][0] + PANEL_LEFT + PanBtnPos[i][2]
1120 		    || MouseY < PanBtnPos[i][1] + PANEL_TOP
1121 		    || MouseY > PanBtnPos[i][1] + PANEL_TOP + PanBtnPos[i][3]) {
1122 			continue;
1123 		}
1124 
1125 		switch (i) {
1126 		case PANBTN_CHARINFO:
1127 			questlog = FALSE;
1128 			chrflag = !chrflag;
1129 			break;
1130 		case PANBTN_QLOG:
1131 			chrflag = FALSE;
1132 			if (!questlog)
1133 				StartQuestlog();
1134 			else
1135 				questlog = FALSE;
1136 			break;
1137 		case PANBTN_AUTOMAP:
1138 			DoAutoMap();
1139 			break;
1140 		case PANBTN_MAINMENU:
1141 			qtextflag = FALSE;
1142 			gamemenu_handle_previous();
1143 			gamemenuOff = FALSE;
1144 			break;
1145 		case PANBTN_INVENTORY:
1146 			sbookflag = FALSE;
1147 			invflag = !invflag;
1148 			if (dropGoldFlag) {
1149 				dropGoldFlag = FALSE;
1150 				dropGoldValue = 0;
1151 			}
1152 			break;
1153 		case PANBTN_SPELLBOOK:
1154 			invflag = FALSE;
1155 			if (dropGoldFlag) {
1156 				dropGoldFlag = FALSE;
1157 				dropGoldValue = 0;
1158 			}
1159 			sbookflag = !sbookflag;
1160 			break;
1161 		case PANBTN_SENDMSG:
1162 			if (talkflag)
1163 				control_reset_talk();
1164 			else
1165 				control_type_message();
1166 			break;
1167 		case PANBTN_FRIENDLY:
1168 			gbFriendlyMode = !gbFriendlyMode;
1169 			break;
1170 		}
1171 	}
1172 
1173 	if (gamemenuOff)
1174 		gamemenu_off();
1175 }
1176 
FreeControlPan()1177 void FreeControlPan()
1178 {
1179 	pBtmBuff.Free();
1180 	pManaBuff.Free();
1181 	pLifeBuff.Free();
1182 	MemFreeDbg(pPanelText);
1183 	MemFreeDbg(pChrPanel);
1184 	MemFreeDbg(pSpellCels);
1185 	MemFreeDbg(pPanelButtons);
1186 	MemFreeDbg(pMultiBtns);
1187 	MemFreeDbg(pTalkBtns);
1188 	MemFreeDbg(pChrButtons);
1189 	MemFreeDbg(pDurIcons);
1190 	MemFreeDbg(pQLogCel);
1191 	MemFreeDbg(pSpellBkCel);
1192 	MemFreeDbg(pSBkBtnCel);
1193 	MemFreeDbg(pSBkIconCels);
1194 	MemFreeDbg(pGBoxBuff);
1195 }
1196 
control_WriteStringToBuffer(BYTE * str)1197 BOOL control_WriteStringToBuffer(BYTE *str)
1198 {
1199 	int k;
1200 	BYTE ichar;
1201 
1202 	k = 0;
1203 	while (*str) {
1204 		ichar = gbFontTransTbl[*str];
1205 		str++;
1206 		k += fontkern[fontframe[ichar]];
1207 		if (k >= 125)
1208 			return FALSE;
1209 	}
1210 
1211 	return TRUE;
1212 }
1213 
CPrintString(CelOutputBuffer out,int y,const char * str,BOOL center,int lines)1214 static void CPrintString(CelOutputBuffer out, int y, const char *str, BOOL center, int lines)
1215 {
1216 	BYTE c;
1217 	const char *tmp;
1218 	int lineOffset, strWidth, sx, sy;
1219 
1220 	lineOffset = 0;
1221 	sx = 177 + PANEL_X;
1222 	sy = lineOffsets[lines][y] + PANEL_Y;
1223 	if (center == TRUE) {
1224 		strWidth = 0;
1225 		tmp = str;
1226 		while (*tmp) {
1227 			c = gbFontTransTbl[(BYTE)*tmp++];
1228 			strWidth += fontkern[fontframe[c]] + 2;
1229 		}
1230 		if (strWidth < 288)
1231 			lineOffset = (288 - strWidth) >> 1;
1232 		sx += lineOffset;
1233 	}
1234 	while (*str) {
1235 		c = gbFontTransTbl[(BYTE)*str++];
1236 		c = fontframe[c];
1237 		lineOffset += fontkern[c] + 2;
1238 		if (c) {
1239 			if (lineOffset < 288) {
1240 				PrintChar(out, sx, sy, c, infoclr);
1241 			}
1242 		}
1243 		sx += fontkern[c] + 2;
1244 	}
1245 }
1246 
PrintInfo(CelOutputBuffer out)1247 static void PrintInfo(CelOutputBuffer out)
1248 {
1249 	int yo, lo, i;
1250 
1251 	if (!talkflag) {
1252 		yo = 0;
1253 		lo = 1;
1254 		if (infostr[0] != '\0') {
1255 			CPrintString(out, 0, infostr, TRUE, pnumlines);
1256 			yo = 1;
1257 			lo = 0;
1258 		}
1259 
1260 		for (i = 0; i < pnumlines; i++) {
1261 			CPrintString(out, i + yo, panelstr[i], pstrjust[i], pnumlines - lo);
1262 		}
1263 	}
1264 }
1265 
DrawInfoBox(CelOutputBuffer out)1266 void DrawInfoBox(CelOutputBuffer out)
1267 {
1268 	int nGold;
1269 
1270 	DrawPanelBox(out, 177, 62, 288, 60, PANEL_X + 177, PANEL_Y + 46);
1271 	if (!panelflag && !trigflag && pcursinvitem == -1 && !spselflag) {
1272 		infostr[0] = '\0';
1273 		infoclr = COL_WHITE;
1274 		ClearPanel();
1275 	}
1276 	if (spselflag || trigflag) {
1277 		infoclr = COL_WHITE;
1278 	} else if (pcurs >= CURSOR_FIRSTITEM) {
1279 		if (plr[myplr].HoldItem._itype == ITYPE_GOLD) {
1280 			nGold = plr[myplr].HoldItem._ivalue;
1281 			sprintf(infostr, "%i gold %s", nGold, get_pieces_str(nGold));
1282 		} else if (!plr[myplr].HoldItem._iStatFlag) {
1283 			ClearPanel();
1284 			AddPanelString("Requirements not met", TRUE);
1285 			pinfoflag = TRUE;
1286 		} else {
1287 			if (plr[myplr].HoldItem._iIdentified)
1288 				strcpy(infostr, plr[myplr].HoldItem._iIName);
1289 			else
1290 				strcpy(infostr, plr[myplr].HoldItem._iName);
1291 			if (plr[myplr].HoldItem._iMagical == ITEM_QUALITY_MAGIC)
1292 				infoclr = COL_BLUE;
1293 			if (plr[myplr].HoldItem._iMagical == ITEM_QUALITY_UNIQUE)
1294 				infoclr = COL_GOLD;
1295 		}
1296 	} else {
1297 		if (pcursitem != -1)
1298 			GetItemStr(pcursitem);
1299 		else if (pcursobj != -1)
1300 			GetObjectStr(pcursobj);
1301 		if (pcursmonst != -1) {
1302 			if (leveltype != DTYPE_TOWN) {
1303 				infoclr = COL_WHITE;
1304 				strcpy(infostr, monster[pcursmonst].mName);
1305 				ClearPanel();
1306 				if (monster[pcursmonst]._uniqtype != 0) {
1307 					infoclr = COL_GOLD;
1308 					PrintUniqueHistory();
1309 				} else {
1310 					PrintMonstHistory(monster[pcursmonst].MType->mtype);
1311 				}
1312 			} else if (pcursitem == -1) {
1313 				strcpy(infostr, towner[pcursmonst]._tName);
1314 			}
1315 		}
1316 		if (pcursplr != -1) {
1317 			infoclr = COL_GOLD;
1318 			strcpy(infostr, plr[pcursplr]._pName);
1319 			ClearPanel();
1320 			sprintf(tempstr, "%s, Level: %i", ClassStrTbl[plr[pcursplr]._pClass], plr[pcursplr]._pLevel);
1321 			AddPanelString(tempstr, TRUE);
1322 			sprintf(tempstr, "Hit Points %i of %i", plr[pcursplr]._pHitPoints >> 6, plr[pcursplr]._pMaxHP >> 6);
1323 			AddPanelString(tempstr, TRUE);
1324 		}
1325 	}
1326 	if (infostr[0] != '\0' || pnumlines != 0)
1327 		PrintInfo(out);
1328 }
1329 
1330 #define ADD_PlrStringXY(out, x, y, width, pszStr, col) MY_PlrStringXY(out, x, y, width, pszStr, col, 1)
1331 
PrintGameStr(CelOutputBuffer out,int x,int y,const char * str,text_color color)1332 void PrintGameStr(CelOutputBuffer out, int x, int y, const char *str, text_color color)
1333 {
1334 	while (*str) {
1335 		BYTE c = gbFontTransTbl[(BYTE)*str++];
1336 		c = fontframe[c];
1337 		if (c)
1338 			PrintChar(out, x, y, c, color);
1339 		x += fontkern[c] + 1;
1340 	}
1341 }
1342 
1343 /**
1344  * @brief Render text string to the given buffer
1345  * @param out Buffer to render to
1346  * @param x Screen coordinate
1347  * @param y Screen coordinate
1348  * @param endX End of line in screen coordinate
1349  * @param pszStr String to print, in Windows-1252 encoding
1350  * @param col text_color color value
1351  * @param base Letter spacing
1352  */
MY_PlrStringXY(CelOutputBuffer out,int x,int y,int endX,const char * pszStr,text_color col,int base)1353 static void MY_PlrStringXY(CelOutputBuffer out, int x, int y, int endX, const char *pszStr, text_color col, int base)
1354 {
1355 	BYTE c;
1356 	const char *tmp;
1357 	int screen_x, line, widthOffset;
1358 
1359 	widthOffset = endX - x + 1;
1360 	line = 0;
1361 	screen_x = 0;
1362 	tmp = pszStr;
1363 	while (*tmp) {
1364 		c = gbFontTransTbl[(BYTE)*tmp++];
1365 		screen_x += fontkern[fontframe[c]] + base;
1366 	}
1367 	if (screen_x < widthOffset)
1368 		line = (widthOffset - screen_x) >> 1;
1369 	x += line;
1370 	while (*pszStr) {
1371 		c = gbFontTransTbl[(BYTE)*pszStr++];
1372 		c = fontframe[c];
1373 		line += fontkern[c] + base;
1374 		if (c) {
1375 			if (line < widthOffset)
1376 				PrintChar(out, x, y, c, col);
1377 		}
1378 		x += fontkern[c] + base;
1379 	}
1380 }
1381 
DrawChr(CelOutputBuffer out)1382 void DrawChr(CelOutputBuffer out)
1383 {
1384 	text_color col = COL_WHITE;
1385 	char chrstr[64];
1386 	int mindam, maxdam;
1387 
1388 	CelDrawTo(out, 0, 351, pChrPanel, 1, SPANEL_WIDTH);
1389 	ADD_PlrStringXY(out, 20, 32, 151, plr[myplr]._pName, COL_WHITE);
1390 
1391 	ADD_PlrStringXY(out, 168, 32, 299, ClassStrTbl[plr[myplr]._pClass], COL_WHITE);
1392 
1393 	sprintf(chrstr, "%i", plr[myplr]._pLevel);
1394 	ADD_PlrStringXY(out, 66, 69, 109, chrstr, COL_WHITE);
1395 
1396 	sprintf(chrstr, "%i", plr[myplr]._pExperience);
1397 	ADD_PlrStringXY(out, 216, 69, 300, chrstr, COL_WHITE);
1398 
1399 	if (plr[myplr]._pLevel == MAXCHARLEVEL - 1) {
1400 		strcpy(chrstr, "None");
1401 		col = COL_GOLD;
1402 	} else {
1403 		sprintf(chrstr, "%i", plr[myplr]._pNextExper);
1404 		col = COL_WHITE;
1405 	}
1406 	ADD_PlrStringXY(out, 216, 97, 300, chrstr, col);
1407 
1408 	sprintf(chrstr, "%i", plr[myplr]._pGold);
1409 	ADD_PlrStringXY(out, 216, 146, 300, chrstr, COL_WHITE);
1410 
1411 	col = COL_WHITE;
1412 	if (plr[myplr]._pIBonusAC > 0)
1413 		col = COL_BLUE;
1414 	if (plr[myplr]._pIBonusAC < 0)
1415 		col = COL_RED;
1416 	sprintf(chrstr, "%i", plr[myplr]._pIBonusAC + plr[myplr]._pIAC + plr[myplr]._pDexterity / 5);
1417 	ADD_PlrStringXY(out, 258, 183, 301, chrstr, col);
1418 
1419 	col = COL_WHITE;
1420 	if (plr[myplr]._pIBonusToHit > 0)
1421 		col = COL_BLUE;
1422 	if (plr[myplr]._pIBonusToHit < 0)
1423 		col = COL_RED;
1424 	sprintf(chrstr, "%i%%", (plr[myplr]._pDexterity >> 1) + plr[myplr]._pIBonusToHit + 50);
1425 	ADD_PlrStringXY(out, 258, 211, 301, chrstr, col);
1426 
1427 	col = COL_WHITE;
1428 	if (plr[myplr]._pIBonusDam > 0)
1429 		col = COL_BLUE;
1430 	if (plr[myplr]._pIBonusDam < 0)
1431 		col = COL_RED;
1432 	mindam = plr[myplr]._pIMinDam;
1433 	mindam += plr[myplr]._pIBonusDam * mindam / 100;
1434 	mindam += plr[myplr]._pIBonusDamMod;
1435 	if (plr[myplr].InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_BOW) {
1436 		if (plr[myplr]._pClass == PC_ROGUE)
1437 			mindam += plr[myplr]._pDamageMod;
1438 		else
1439 			mindam += plr[myplr]._pDamageMod >> 1;
1440 	} else {
1441 		mindam += plr[myplr]._pDamageMod;
1442 	}
1443 	maxdam = plr[myplr]._pIMaxDam;
1444 	maxdam += plr[myplr]._pIBonusDam * maxdam / 100;
1445 	maxdam += plr[myplr]._pIBonusDamMod;
1446 	if (plr[myplr].InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_BOW) {
1447 		if (plr[myplr]._pClass == PC_ROGUE)
1448 			maxdam += plr[myplr]._pDamageMod;
1449 		else
1450 			maxdam += plr[myplr]._pDamageMod >> 1;
1451 	} else {
1452 		maxdam += plr[myplr]._pDamageMod;
1453 	}
1454 	sprintf(chrstr, "%i-%i", mindam, maxdam);
1455 	if (mindam >= 100 || maxdam >= 100)
1456 		MY_PlrStringXY(out, 254, 239, 305, chrstr, col, -1);
1457 	else
1458 		MY_PlrStringXY(out, 258, 239, 301, chrstr, col, 0);
1459 
1460 	if (plr[myplr]._pMagResist == 0)
1461 		col = COL_WHITE;
1462 	else
1463 		col = COL_BLUE;
1464 	if (plr[myplr]._pMagResist < MAXRESIST) {
1465 		sprintf(chrstr, "%i%%", plr[myplr]._pMagResist);
1466 	} else {
1467 		col = COL_GOLD;
1468 		sprintf(chrstr, "MAX");
1469 	}
1470 	ADD_PlrStringXY(out, 257, 276, 300, chrstr, col);
1471 
1472 	if (plr[myplr]._pFireResist == 0)
1473 		col = COL_WHITE;
1474 	else
1475 		col = COL_BLUE;
1476 	if (plr[myplr]._pFireResist < MAXRESIST) {
1477 		sprintf(chrstr, "%i%%", plr[myplr]._pFireResist);
1478 	} else {
1479 		col = COL_GOLD;
1480 		sprintf(chrstr, "MAX");
1481 	}
1482 	ADD_PlrStringXY(out, 257, 304, 300, chrstr, col);
1483 
1484 	if (plr[myplr]._pLghtResist == 0)
1485 		col = COL_WHITE;
1486 	else
1487 		col = COL_BLUE;
1488 	if (plr[myplr]._pLghtResist < MAXRESIST) {
1489 		sprintf(chrstr, "%i%%", plr[myplr]._pLghtResist);
1490 	} else {
1491 		col = COL_GOLD;
1492 		sprintf(chrstr, "MAX");
1493 	}
1494 	ADD_PlrStringXY(out, 257, 332, 300, chrstr, col);
1495 
1496 	col = COL_WHITE;
1497 	sprintf(chrstr, "%i", plr[myplr]._pBaseStr);
1498 	if (MaxStats[plr[myplr]._pClass][ATTRIB_STR] == plr[myplr]._pBaseStr)
1499 		col = COL_GOLD;
1500 	ADD_PlrStringXY(out, 95, 155, 126, chrstr, col);
1501 
1502 	col = COL_WHITE;
1503 	sprintf(chrstr, "%i", plr[myplr]._pBaseMag);
1504 	if (MaxStats[plr[myplr]._pClass][ATTRIB_MAG] == plr[myplr]._pBaseMag)
1505 		col = COL_GOLD;
1506 	ADD_PlrStringXY(out, 95, 183, 126, chrstr, col);
1507 
1508 	col = COL_WHITE;
1509 	sprintf(chrstr, "%i", plr[myplr]._pBaseDex);
1510 	if (MaxStats[plr[myplr]._pClass][ATTRIB_DEX] == plr[myplr]._pBaseDex)
1511 		col = COL_GOLD;
1512 	ADD_PlrStringXY(out, 95, 211, 126, chrstr, col);
1513 
1514 	col = COL_WHITE;
1515 	sprintf(chrstr, "%i", plr[myplr]._pBaseVit);
1516 	if (MaxStats[plr[myplr]._pClass][ATTRIB_VIT] == plr[myplr]._pBaseVit)
1517 		col = COL_GOLD;
1518 	ADD_PlrStringXY(out, 95, 239, 126, chrstr, col);
1519 
1520 	col = COL_WHITE;
1521 	if (plr[myplr]._pStrength > plr[myplr]._pBaseStr)
1522 		col = COL_BLUE;
1523 	if (plr[myplr]._pStrength < plr[myplr]._pBaseStr)
1524 		col = COL_RED;
1525 	sprintf(chrstr, "%i", plr[myplr]._pStrength);
1526 	ADD_PlrStringXY(out, 143, 155, 173, chrstr, col);
1527 
1528 	col = COL_WHITE;
1529 	if (plr[myplr]._pMagic > plr[myplr]._pBaseMag)
1530 		col = COL_BLUE;
1531 	if (plr[myplr]._pMagic < plr[myplr]._pBaseMag)
1532 		col = COL_RED;
1533 	sprintf(chrstr, "%i", plr[myplr]._pMagic);
1534 	ADD_PlrStringXY(out, 143, 183, 173, chrstr, col);
1535 
1536 	col = COL_WHITE;
1537 	if (plr[myplr]._pDexterity > plr[myplr]._pBaseDex)
1538 		col = COL_BLUE;
1539 	if (plr[myplr]._pDexterity < plr[myplr]._pBaseDex)
1540 		col = COL_RED;
1541 	sprintf(chrstr, "%i", plr[myplr]._pDexterity);
1542 	ADD_PlrStringXY(out, 143, 211, 173, chrstr, col);
1543 
1544 	col = COL_WHITE;
1545 	if (plr[myplr]._pVitality > plr[myplr]._pBaseVit)
1546 		col = COL_BLUE;
1547 	if (plr[myplr]._pVitality < plr[myplr]._pBaseVit)
1548 		col = COL_RED;
1549 	sprintf(chrstr, "%i", plr[myplr]._pVitality);
1550 	ADD_PlrStringXY(out, 143, 239, 173, chrstr, col);
1551 
1552 	if (plr[myplr]._pStatPts > 0) {
1553 		if (CalcStatDiff(myplr) < plr[myplr]._pStatPts) {
1554 			plr[myplr]._pStatPts = CalcStatDiff(myplr);
1555 		}
1556 	}
1557 	if (plr[myplr]._pStatPts > 0) {
1558 		sprintf(chrstr, "%i", plr[myplr]._pStatPts);
1559 		ADD_PlrStringXY(out, 95, 266, 126, chrstr, COL_RED);
1560 		plr_class pc = plr[myplr]._pClass;
1561 		if (plr[myplr]._pBaseStr < MaxStats[pc][ATTRIB_STR])
1562 			CelDrawTo(out, 137, 159, pChrButtons, chrbtn[ATTRIB_STR] + 2, 41);
1563 		if (plr[myplr]._pBaseMag < MaxStats[pc][ATTRIB_MAG])
1564 			CelDrawTo(out, 137, 187, pChrButtons, chrbtn[ATTRIB_MAG] + 4, 41);
1565 		if (plr[myplr]._pBaseDex < MaxStats[pc][ATTRIB_DEX])
1566 			CelDrawTo(out, 137, 216, pChrButtons, chrbtn[ATTRIB_DEX] + 6, 41);
1567 		if (plr[myplr]._pBaseVit < MaxStats[pc][ATTRIB_VIT])
1568 			CelDrawTo(out, 137, 244, pChrButtons, chrbtn[ATTRIB_VIT] + 8, 41);
1569 	}
1570 
1571 	if (plr[myplr]._pMaxHP > plr[myplr]._pMaxHPBase)
1572 		col = COL_BLUE;
1573 	else
1574 		col = COL_WHITE;
1575 	sprintf(chrstr, "%i", plr[myplr]._pMaxHP >> 6);
1576 	ADD_PlrStringXY(out, 95, 304, 126, chrstr, col);
1577 	if (plr[myplr]._pHitPoints != plr[myplr]._pMaxHP)
1578 		col = COL_RED;
1579 	sprintf(chrstr, "%i", plr[myplr]._pHitPoints >> 6);
1580 	ADD_PlrStringXY(out, 143, 304, 174, chrstr, col);
1581 
1582 	if (plr[myplr]._pMaxMana > plr[myplr]._pMaxManaBase)
1583 		col = COL_BLUE;
1584 	else
1585 		col = COL_WHITE;
1586 	sprintf(chrstr, "%i", plr[myplr]._pMaxMana >> 6);
1587 	ADD_PlrStringXY(out, 95, 332, 126, chrstr, col);
1588 	if (plr[myplr]._pMana != plr[myplr]._pMaxMana)
1589 		col = COL_RED;
1590 	sprintf(chrstr, "%i", plr[myplr]._pMana >> 6);
1591 	ADD_PlrStringXY(out, 143, 332, 174, chrstr, col);
1592 }
1593 
CheckLvlBtn()1594 void CheckLvlBtn()
1595 {
1596 	if (!lvlbtndown && MouseX >= 40 + PANEL_LEFT && MouseX <= 81 + PANEL_LEFT && MouseY >= -39 + PANEL_TOP && MouseY <= -17 + PANEL_TOP)
1597 		lvlbtndown = TRUE;
1598 }
1599 
ReleaseLvlBtn()1600 void ReleaseLvlBtn()
1601 {
1602 	if (MouseX >= 40 + PANEL_LEFT && MouseX <= 81 + PANEL_LEFT && MouseY >= -39 + PANEL_TOP && MouseY <= -17 + PANEL_TOP)
1603 		chrflag = TRUE;
1604 	lvlbtndown = FALSE;
1605 }
1606 
DrawLevelUpIcon(CelOutputBuffer out)1607 void DrawLevelUpIcon(CelOutputBuffer out)
1608 {
1609 	int nCel;
1610 
1611 	if (stextflag == STORE_NONE) {
1612 		nCel = lvlbtndown ? 3 : 2;
1613 		ADD_PlrStringXY(out, PANEL_LEFT + 0, PANEL_TOP - 49, PANEL_LEFT + 120, "Level Up", COL_WHITE);
1614 		CelDrawTo(out, 40 + PANEL_X, -17 + PANEL_Y, pChrButtons, nCel, 41);
1615 	}
1616 }
1617 
CheckChrBtns()1618 void CheckChrBtns()
1619 {
1620 	int i, x, y;
1621 
1622 	if (!chrbtnactive && plr[myplr]._pStatPts) {
1623 		plr_class pc = plr[myplr]._pClass;
1624 		for (i = 0; i < 4; i++) {
1625 			switch (i) {
1626 			case ATTRIB_STR:
1627 				if (plr[myplr]._pBaseStr >= MaxStats[pc][ATTRIB_STR])
1628 					continue;
1629 				break;
1630 			case ATTRIB_MAG:
1631 				if (plr[myplr]._pBaseMag >= MaxStats[pc][ATTRIB_MAG])
1632 					continue;
1633 				break;
1634 			case ATTRIB_DEX:
1635 				if (plr[myplr]._pBaseDex >= MaxStats[pc][ATTRIB_DEX])
1636 					continue;
1637 				break;
1638 			case ATTRIB_VIT:
1639 				if (plr[myplr]._pBaseVit >= MaxStats[pc][ATTRIB_VIT])
1640 					continue;
1641 				break;
1642 			default:
1643 				continue;
1644 			}
1645 			x = ChrBtnsRect[i].x + ChrBtnsRect[i].w;
1646 			y = ChrBtnsRect[i].y + ChrBtnsRect[i].h;
1647 			if (MouseX >= ChrBtnsRect[i].x
1648 			    && MouseX <= x
1649 			    && MouseY >= ChrBtnsRect[i].y
1650 			    && MouseY <= y) {
1651 				chrbtn[i] = TRUE;
1652 				chrbtnactive = TRUE;
1653 			}
1654 		}
1655 	}
1656 }
1657 
CapStatPointsToAdd(int remainingStatPoints,const PlayerStruct & player,attribute_id attribute)1658 int CapStatPointsToAdd(int remainingStatPoints, const PlayerStruct &player, attribute_id attribute)
1659 {
1660 	int pointsToReachCap = player.GetMaximumAttributeValue(attribute) - player.GetBaseAttributeValue(attribute);
1661 
1662 	return std::min(remainingStatPoints, pointsToReachCap);
1663 }
1664 
ReleaseChrBtns(bool addAllStatPoints)1665 void ReleaseChrBtns(bool addAllStatPoints)
1666 {
1667 	int i;
1668 
1669 	chrbtnactive = FALSE;
1670 	for (i = 0; i < 4; ++i) {
1671 		if (chrbtn[i]) {
1672 			chrbtn[i] = FALSE;
1673 			if (MouseX >= ChrBtnsRect[i].x
1674 			    && MouseX <= ChrBtnsRect[i].x + ChrBtnsRect[i].w
1675 			    && MouseY >= ChrBtnsRect[i].y
1676 			    && MouseY <= ChrBtnsRect[i].y + ChrBtnsRect[i].h) {
1677 				PlayerStruct &player = plr[myplr];
1678 				int statPointsToAdd = addAllStatPoints ? player._pStatPts : 1;
1679 				switch (i) {
1680 				case 0:
1681 					statPointsToAdd = CapStatPointsToAdd(statPointsToAdd, player, attribute_id::ATTRIB_STR);
1682 					NetSendCmdParam1(TRUE, CMD_ADDSTR, statPointsToAdd);
1683 					player._pStatPts -= statPointsToAdd;
1684 					break;
1685 				case 1:
1686 					statPointsToAdd = CapStatPointsToAdd(statPointsToAdd, player, attribute_id::ATTRIB_MAG);
1687 					NetSendCmdParam1(TRUE, CMD_ADDMAG, statPointsToAdd);
1688 					player._pStatPts -= statPointsToAdd;
1689 					break;
1690 				case 2:
1691 					statPointsToAdd = CapStatPointsToAdd(statPointsToAdd, player, attribute_id::ATTRIB_DEX);
1692 					NetSendCmdParam1(TRUE, CMD_ADDDEX, statPointsToAdd);
1693 					player._pStatPts -= statPointsToAdd;
1694 					break;
1695 				case 3:
1696 					statPointsToAdd = CapStatPointsToAdd(statPointsToAdd, player, attribute_id::ATTRIB_VIT);
1697 					NetSendCmdParam1(TRUE, CMD_ADDVIT, statPointsToAdd);
1698 					player._pStatPts -= statPointsToAdd;
1699 					break;
1700 				}
1701 			}
1702 		}
1703 	}
1704 }
1705 
DrawDurIcon4Item(CelOutputBuffer out,ItemStruct * pItem,int x,int c)1706 static int DrawDurIcon4Item(CelOutputBuffer out, ItemStruct *pItem, int x, int c)
1707 {
1708 	if (pItem->isEmpty())
1709 		return x;
1710 	if (pItem->_iDurability > 5)
1711 		return x;
1712 	if (c == 0) {
1713 		switch (pItem->_itype) {
1714 		case ITYPE_SWORD:
1715 			c = 2;
1716 			break;
1717 		case ITYPE_AXE:
1718 			c = 6;
1719 			break;
1720 		case ITYPE_BOW:
1721 			c = 7;
1722 			break;
1723 		case ITYPE_MACE:
1724 			c = 5;
1725 			break;
1726 		case ITYPE_STAFF:
1727 			c = 8;
1728 			break;
1729 		default:
1730 			c = 1;
1731 			break;
1732 		}
1733 	}
1734 	if (pItem->_iDurability > 2)
1735 		c += 8;
1736 	CelDrawTo(out, x, -17 + PANEL_Y, pDurIcons, c, 32);
1737 	return x - 32 - 8;
1738 }
1739 
DrawDurIcon(CelOutputBuffer out)1740 void DrawDurIcon(CelOutputBuffer out)
1741 {
1742 	PlayerStruct *p;
1743 	int x;
1744 
1745 	bool hasRoomBetweenPanels = gnScreenWidth >= PANEL_WIDTH + 16 + (32 + 8 + 32 + 8 + 32 + 8 + 32) + 16;
1746 	bool hasRoomUnderPanels = gnScreenHeight >= SPANEL_HEIGHT + PANEL_HEIGHT + 16 + 32 + 16;
1747 
1748 	if (!hasRoomBetweenPanels && !hasRoomUnderPanels) {
1749 		if ((chrflag || questlog) && (invflag || sbookflag))
1750 			return;
1751 	}
1752 
1753 	x = PANEL_X + PANEL_WIDTH - 32 - 16;
1754 	if (!hasRoomUnderPanels) {
1755 		if (invflag || sbookflag)
1756 			x -= SPANEL_WIDTH - (gnScreenWidth - PANEL_WIDTH) / 2;
1757 	}
1758 
1759 	p = &plr[myplr];
1760 	x = DrawDurIcon4Item(out, &p->InvBody[INVLOC_HEAD], x, 4);
1761 	x = DrawDurIcon4Item(out, &p->InvBody[INVLOC_CHEST], x, 3);
1762 	x = DrawDurIcon4Item(out, &p->InvBody[INVLOC_HAND_LEFT], x, 0);
1763 	DrawDurIcon4Item(out, &p->InvBody[INVLOC_HAND_RIGHT], x, 0);
1764 }
1765 
RedBack(CelOutputBuffer out)1766 void RedBack(CelOutputBuffer out)
1767 {
1768 	int idx;
1769 
1770 	idx = light4flag ? 1536 : 4608;
1771 	int w, h;
1772 	BYTE *dst, *tbl;
1773 
1774 	if (leveltype != DTYPE_HELL) {
1775 		dst = out.begin();
1776 		tbl = &pLightTbl[idx];
1777 		for (h = gnViewportHeight; h; h--, dst += out.pitch() - gnScreenWidth) {
1778 			for (w = gnScreenWidth; w; w--) {
1779 				*dst = tbl[*dst];
1780 				dst++;
1781 			}
1782 		}
1783 	} else {
1784 		dst = out.begin();
1785 		tbl = &pLightTbl[idx];
1786 		for (h = gnViewportHeight; h; h--, dst += out.pitch() - gnScreenWidth) {
1787 			for (w = gnScreenWidth; w; w--) {
1788 				if (*dst >= 32)
1789 					*dst = tbl[*dst];
1790 				dst++;
1791 			}
1792 		}
1793 	}
1794 }
1795 
PrintSBookStr(CelOutputBuffer out,int x,int y,BOOL cjustflag,const char * pszStr,text_color col)1796 static void PrintSBookStr(CelOutputBuffer out, int x, int y, BOOL cjustflag, const char *pszStr, text_color col)
1797 {
1798 	BYTE c;
1799 	const char *tmp;
1800 	int screen_x, line, sx;
1801 
1802 	sx = x + RIGHT_PANEL_X + SPLICONLENGTH;
1803 	line = 0;
1804 	if (cjustflag) {
1805 		screen_x = 0;
1806 		tmp = pszStr;
1807 		while (*tmp) {
1808 			c = gbFontTransTbl[(BYTE)*tmp++];
1809 			screen_x += fontkern[fontframe[c]] + 1;
1810 		}
1811 		if (screen_x < 222)
1812 			line = (222 - screen_x) >> 1;
1813 		sx += line;
1814 	}
1815 	while (*pszStr) {
1816 		c = gbFontTransTbl[(BYTE)*pszStr++];
1817 		c = fontframe[c];
1818 		line += fontkern[c] + 1;
1819 		if (c) {
1820 			if (line <= 222)
1821 				PrintChar(out, sx, y, c, col);
1822 		}
1823 		sx += fontkern[c] + 1;
1824 	}
1825 }
1826 
GetSBookTrans(int ii,BOOL townok)1827 char GetSBookTrans(int ii, BOOL townok)
1828 {
1829 	char st;
1830 
1831 	if ((plr[myplr]._pClass == PC_MONK) && (ii == SPL_SEARCH))
1832 		return RSPLTYPE_SKILL;
1833 	st = RSPLTYPE_SPELL;
1834 	if (plr[myplr]._pISpells & GetSpellBitmask(ii)) {
1835 		st = RSPLTYPE_CHARGES;
1836 	}
1837 	if (plr[myplr]._pAblSpells & GetSpellBitmask(ii)) {
1838 		st = RSPLTYPE_SKILL;
1839 	}
1840 	if (st == RSPLTYPE_SPELL) {
1841 		if (!CheckSpell(myplr, ii, RSPLTYPE_SPELL, TRUE)) {
1842 			st = RSPLTYPE_INVALID;
1843 		}
1844 		if ((char)(plr[myplr]._pSplLvl[ii] + plr[myplr]._pISplLvlAdd) <= 0) {
1845 			st = RSPLTYPE_INVALID;
1846 		}
1847 	}
1848 	if (townok && currlevel == 0 && st != RSPLTYPE_INVALID && !spelldata[ii].sTownSpell) {
1849 		st = RSPLTYPE_INVALID;
1850 	}
1851 
1852 	return st;
1853 }
1854 
DrawSpellBook(CelOutputBuffer out)1855 void DrawSpellBook(CelOutputBuffer out)
1856 {
1857 	int i, sn, mana, lvl, yp, min, max;
1858 	char st;
1859 
1860 	CelDrawTo(out, RIGHT_PANEL_X, 351, pSpellBkCel, 1, SPANEL_WIDTH);
1861 	if (gbIsHellfire && sbooktab < 5) {
1862 		CelDrawTo(out, RIGHT_PANEL_X + 61 * sbooktab + 7, 348, pSBkBtnCel, sbooktab + 1, 61);
1863 	} else {
1864 		// BUGFIX: rendering of page 3 and page 4 buttons are both off-by-one pixel (fixed).
1865 		int sx = RIGHT_PANEL_X + 76 * sbooktab + 7;
1866 		if (sbooktab == 2 || sbooktab == 3) {
1867 			sx++;
1868 		}
1869 		CelDrawTo(out, sx, 348, pSBkBtnCel, sbooktab + 1, 76);
1870 	}
1871 	Uint64 spl = plr[myplr]._pMemSpells | plr[myplr]._pISpells | plr[myplr]._pAblSpells;
1872 
1873 	yp = 55;
1874 	for (i = 1; i < 8; i++) {
1875 		sn = SpellPages[sbooktab][i - 1];
1876 		if (sn != -1 && spl & GetSpellBitmask(sn)) {
1877 			st = GetSBookTrans(sn, TRUE);
1878 			SetSpellTrans(st);
1879 			DrawSpellCel(out, RIGHT_PANEL_X + 11, yp, pSBkIconCels, SpellITbl[sn], 37);
1880 			if (sn == plr[myplr]._pRSpell && st == plr[myplr]._pRSplType) {
1881 				SetSpellTrans(RSPLTYPE_SKILL);
1882 				DrawSpellCel(out, RIGHT_PANEL_X + 11, yp, pSBkIconCels, SPLICONLAST, 37);
1883 			}
1884 			PrintSBookStr(out, 10, yp - 23, FALSE, spelldata[sn].sNameText, COL_WHITE);
1885 			switch (GetSBookTrans(sn, FALSE)) {
1886 			case RSPLTYPE_SKILL:
1887 				strcpy(tempstr, "Skill");
1888 				break;
1889 			case RSPLTYPE_CHARGES:
1890 				sprintf(tempstr, "Staff (%i charges)", plr[myplr].InvBody[INVLOC_HAND_LEFT]._iCharges);
1891 				break;
1892 			default:
1893 				mana = GetManaAmount(myplr, sn) >> 6;
1894 				GetDamageAmt(sn, &min, &max);
1895 				if (min != -1) {
1896 					sprintf(tempstr, "Mana: %i  Dam: %i - %i", mana, min, max);
1897 				} else {
1898 					sprintf(tempstr, "Mana: %i   Dam: n/a", mana);
1899 				}
1900 				if (sn == SPL_BONESPIRIT) {
1901 					sprintf(tempstr, "Mana: %i  Dam: 1/3 tgt hp", mana);
1902 				}
1903 				PrintSBookStr(out, 10, yp - 1, FALSE, tempstr, COL_WHITE);
1904 				lvl = plr[myplr]._pSplLvl[sn] + plr[myplr]._pISplLvlAdd;
1905 				if (lvl < 0) {
1906 					lvl = 0;
1907 				}
1908 				if (lvl == 0) {
1909 					sprintf(tempstr, "Spell Level 0 - Unusable");
1910 				} else {
1911 					sprintf(tempstr, "Spell Level %i", lvl);
1912 				}
1913 				break;
1914 			}
1915 			PrintSBookStr(out, 10, yp - 12, FALSE, tempstr, COL_WHITE);
1916 		}
1917 		yp += 43;
1918 	}
1919 }
1920 
CheckSBook()1921 void CheckSBook()
1922 {
1923 	if (MouseX >= RIGHT_PANEL + 11 && MouseX < RIGHT_PANEL + 48 && MouseY >= 18 && MouseY < 314) {
1924 		spell_id sn = SpellPages[sbooktab][(MouseY - 18) / 43];
1925 		Uint64 spl = plr[myplr]._pMemSpells | plr[myplr]._pISpells | plr[myplr]._pAblSpells;
1926 		if (sn != SPL_INVALID && spl & GetSpellBitmask(sn)) {
1927 			spell_type st = RSPLTYPE_SPELL;
1928 			if (plr[myplr]._pISpells & GetSpellBitmask(sn)) {
1929 				st = RSPLTYPE_CHARGES;
1930 			}
1931 			if (plr[myplr]._pAblSpells & GetSpellBitmask(sn)) {
1932 				st = RSPLTYPE_SKILL;
1933 			}
1934 			plr[myplr]._pRSpell = sn;
1935 			plr[myplr]._pRSplType = st;
1936 			force_redraw = 255;
1937 		}
1938 	}
1939 	if (MouseX >= RIGHT_PANEL + 7 && MouseX < RIGHT_PANEL + 311 && MouseY >= SPANEL_WIDTH && MouseY < 349) {
1940 		sbooktab = (MouseX - (RIGHT_PANEL + 7)) / (gbIsHellfire ? 61 : 76);
1941 	}
1942 }
1943 
get_pieces_str(int nGold)1944 const char *get_pieces_str(int nGold)
1945 {
1946 	const char *result;
1947 
1948 	result = "piece";
1949 	if (nGold != 1)
1950 		result = "pieces";
1951 	return result;
1952 }
1953 
DrawGoldSplit(CelOutputBuffer out,int amount)1954 void DrawGoldSplit(CelOutputBuffer out, int amount)
1955 {
1956 	int screen_x, i;
1957 
1958 	screen_x = 0;
1959 	CelDrawTo(out, 351, 178, pGBoxBuff, 1, 261);
1960 	sprintf(tempstr, "You have %u gold", initialDropGoldValue);
1961 	ADD_PlrStringXY(out, 366, 87, 600, tempstr, COL_GOLD);
1962 	sprintf(tempstr, "%s.  How many do", get_pieces_str(initialDropGoldValue));
1963 	ADD_PlrStringXY(out, 366, 103, 600, tempstr, COL_GOLD);
1964 	ADD_PlrStringXY(out, 366, 121, 600, "you want to remove?", COL_GOLD);
1965 	if (amount > 0) {
1966 		sprintf(tempstr, "%u", amount);
1967 		PrintGameStr(out, 388, 140, tempstr, COL_WHITE);
1968 	}
1969 	if (amount > 0) {
1970 		for (i = 0; i < tempstr[i]; i++) {
1971 			BYTE c = fontframe[gbFontTransTbl[(BYTE)tempstr[i]]];
1972 			screen_x += fontkern[c] + 1;
1973 		}
1974 		screen_x += 388;
1975 	} else {
1976 		screen_x = 386;
1977 	}
1978 	CelDrawTo(out, screen_x, 140, pSPentSpn2Cels, PentSpn2Spin(), 12);
1979 }
1980 
control_drop_gold(char vkey)1981 void control_drop_gold(char vkey)
1982 {
1983 	char input[6];
1984 
1985 	if (plr[myplr]._pHitPoints >> 6 <= 0) {
1986 		dropGoldFlag = FALSE;
1987 		dropGoldValue = 0;
1988 		return;
1989 	}
1990 
1991 	memset(input, 0, sizeof(input));
1992 	snprintf(input, sizeof(input), "%d", dropGoldValue);
1993 	if (vkey == DVL_VK_RETURN) {
1994 		if (dropGoldValue > 0)
1995 			control_remove_gold(myplr, initialDropGoldIndex);
1996 		dropGoldFlag = FALSE;
1997 	} else if (vkey == DVL_VK_ESCAPE) {
1998 		dropGoldFlag = FALSE;
1999 		dropGoldValue = 0;
2000 	} else if (vkey == DVL_VK_BACK) {
2001 		input[strlen(input) - 1] = '\0';
2002 		dropGoldValue = atoi(input);
2003 	} else if (vkey - '0' >= 0 && vkey - '0' <= 9) {
2004 		if (dropGoldValue != 0 || atoi(input) <= initialDropGoldValue) {
2005 			input[strlen(input)] = vkey;
2006 			if (atoi(input) > initialDropGoldValue)
2007 				return;
2008 			if (strlen(input) > strlen(input))
2009 				return;
2010 		} else {
2011 			input[0] = vkey;
2012 		}
2013 		dropGoldValue = atoi(input);
2014 	}
2015 }
2016 
control_remove_gold(int pnum,int gold_index)2017 void control_remove_gold(int pnum, int gold_index)
2018 {
2019 	int gi;
2020 
2021 	if (gold_index <= INVITEM_INV_LAST) {
2022 		gi = gold_index - INVITEM_INV_FIRST;
2023 		plr[pnum].InvList[gi]._ivalue -= dropGoldValue;
2024 		if (plr[pnum].InvList[gi]._ivalue > 0)
2025 			SetGoldCurs(pnum, gi);
2026 		else
2027 			RemoveInvItem(pnum, gi);
2028 	} else {
2029 		gi = gold_index - INVITEM_BELT_FIRST;
2030 		plr[pnum].SpdList[gi]._ivalue -= dropGoldValue;
2031 		if (plr[pnum].SpdList[gi]._ivalue > 0)
2032 			SetSpdbarGoldCurs(pnum, gi);
2033 		else
2034 			RemoveSpdBarItem(pnum, gi);
2035 	}
2036 	SetPlrHandItem(&plr[pnum].HoldItem, IDI_GOLD);
2037 	GetGoldSeed(pnum, &plr[pnum].HoldItem);
2038 	plr[pnum].HoldItem._ivalue = dropGoldValue;
2039 	plr[pnum].HoldItem._iStatFlag = TRUE;
2040 	control_set_gold_curs(pnum);
2041 	plr[pnum]._pGold = CalculateGold(pnum);
2042 	dropGoldValue = 0;
2043 }
2044 
control_set_gold_curs(int pnum)2045 void control_set_gold_curs(int pnum)
2046 {
2047 	SetPlrHandGoldCurs(&plr[pnum].HoldItem);
2048 	NewCursor(plr[pnum].HoldItem._iCurs + CURSOR_FIRSTITEM);
2049 }
2050 
control_print_talk_msg(CelOutputBuffer out,char * msg,int * x,int y,text_color color)2051 static char *control_print_talk_msg(CelOutputBuffer out, char *msg, int *x, int y, text_color color)
2052 {
2053 	BYTE c;
2054 	int width;
2055 
2056 	*x += 200;
2057 	y += 22 + PANEL_Y;
2058 	width = *x;
2059 	while (*msg) {
2060 
2061 		c = gbFontTransTbl[(BYTE)*msg];
2062 		c = fontframe[c];
2063 		width += fontkern[c] + 1;
2064 		if (width > 450 + PANEL_X)
2065 			return msg;
2066 		msg++;
2067 		if (c != 0) {
2068 			PrintChar(out, *x, y, c, color);
2069 		}
2070 		*x += fontkern[c] + 1;
2071 	}
2072 	return NULL;
2073 }
2074 
DrawTalkPan(CelOutputBuffer out)2075 void DrawTalkPan(CelOutputBuffer out)
2076 {
2077 	int i, off, talk_btn, nCel, x;
2078 	char *msg;
2079 
2080 	if (!talkflag)
2081 		return;
2082 
2083 	DrawPanelBox(out, 175, sgbPlrTalkTbl + 20, 294, 5, PANEL_X + 175, PANEL_Y + 4);
2084 	off = 0;
2085 	for (i = 293; i > 283; off++, i--) {
2086 		DrawPanelBox(out, (off >> 1) + 175, sgbPlrTalkTbl + off + 25, i, 1, (off >> 1) + PANEL_X + 175, off + PANEL_Y + 9);
2087 	}
2088 	DrawPanelBox(out, 185, sgbPlrTalkTbl + 35, 274, 30, PANEL_X + 185, PANEL_Y + 19);
2089 	DrawPanelBox(out, 180, sgbPlrTalkTbl + 65, 284, 5, PANEL_X + 180, PANEL_Y + 49);
2090 	for (i = 0; i < 10; i++) {
2091 		DrawPanelBox(out, 180, sgbPlrTalkTbl + i + 70, i + 284, 1, PANEL_X + 180, i + PANEL_Y + 54);
2092 	}
2093 	DrawPanelBox(out, 170, sgbPlrTalkTbl + 80, 310, 55, PANEL_X + 170, PANEL_Y + 64);
2094 	msg = sgszTalkMsg;
2095 	for (i = 0; i < 39; i += 13) {
2096 		x = 0 + PANEL_LEFT;
2097 		msg = control_print_talk_msg(out, msg, &x, i, COL_WHITE);
2098 		if (!msg)
2099 			break;
2100 	}
2101 	if (msg)
2102 		*msg = '\0';
2103 	CelDrawTo(out, x, i + 22 + PANEL_Y, pSPentSpn2Cels, PentSpn2Spin(), 12);
2104 	talk_btn = 0;
2105 	for (i = 0; i < 4; i++) {
2106 		if (i == myplr)
2107 			continue;
2108 		text_color color = COL_RED;
2109 		if (whisper[i]) {
2110 			color = COL_GOLD;
2111 			if (talkbtndown[talk_btn]) {
2112 				if (talk_btn != 0)
2113 					nCel = 4;
2114 				else
2115 					nCel = 3;
2116 				CelDrawTo(out, 172 + PANEL_X, 84 + 18 * talk_btn + PANEL_Y, pTalkBtns, nCel, 61);
2117 			}
2118 		} else {
2119 			if (talk_btn != 0)
2120 				nCel = 2;
2121 			else
2122 				nCel = 1;
2123 			if (talkbtndown[talk_btn])
2124 				nCel += 4;
2125 			CelDrawTo(out, 172 + PANEL_X, 84 + 18 * talk_btn + PANEL_Y, pTalkBtns, nCel, 61);
2126 		}
2127 		if (plr[i].plractive) {
2128 			x = 46 + PANEL_LEFT;
2129 			control_print_talk_msg(out, plr[i]._pName, &x, 60 + talk_btn * 18, color);
2130 		}
2131 
2132 		talk_btn++;
2133 	}
2134 }
2135 
control_check_talk_btn()2136 BOOL control_check_talk_btn()
2137 {
2138 	int i;
2139 
2140 	if (!talkflag)
2141 		return FALSE;
2142 
2143 	if (MouseX < 172 + PANEL_LEFT)
2144 		return FALSE;
2145 	if (MouseY < 69 + PANEL_TOP)
2146 		return FALSE;
2147 	if (MouseX > 233 + PANEL_LEFT)
2148 		return FALSE;
2149 	if (MouseY > 123 + PANEL_TOP)
2150 		return FALSE;
2151 
2152 	for (i = 0; i < sizeof(talkbtndown) / sizeof(talkbtndown[0]); i++) {
2153 		talkbtndown[i] = FALSE;
2154 	}
2155 
2156 	talkbtndown[(MouseY - (69 + PANEL_TOP)) / 18] = TRUE;
2157 
2158 	return TRUE;
2159 }
2160 
control_release_talk_btn()2161 void control_release_talk_btn()
2162 {
2163 	int i, p, off;
2164 
2165 	if (talkflag) {
2166 		for (i = 0; i < sizeof(talkbtndown) / sizeof(talkbtndown[0]); i++)
2167 			talkbtndown[i] = FALSE;
2168 		if (MouseX >= 172 + PANEL_LEFT && MouseY >= 69 + PANEL_TOP && MouseX <= 233 + PANEL_LEFT && MouseY <= 123 + PANEL_TOP) {
2169 			off = (MouseY - (69 + PANEL_TOP)) / 18;
2170 
2171 			for (p = 0; p < MAX_PLRS && off != -1; p++) {
2172 				if (p != myplr)
2173 					off--;
2174 			}
2175 			if (p <= MAX_PLRS)
2176 				whisper[p - 1] = !whisper[p - 1];
2177 		}
2178 	}
2179 }
2180 
control_reset_talk_msg(char * msg)2181 void control_reset_talk_msg(char *msg)
2182 {
2183 	int i, pmask;
2184 	pmask = 0;
2185 
2186 	for (i = 0; i < MAX_PLRS; i++) {
2187 		if (whisper[i])
2188 			pmask |= 1 << i;
2189 	}
2190 	NetSendCmdString(pmask, sgszTalkMsg);
2191 }
2192 
control_type_message()2193 void control_type_message()
2194 {
2195 	int i;
2196 
2197 	if (!gbIsMultiplayer) {
2198 		return;
2199 	}
2200 
2201 	talkflag = TRUE;
2202 	sgszTalkMsg[0] = '\0';
2203 	for (i = 0; i < 3; i++) {
2204 		talkbtndown[i] = FALSE;
2205 	}
2206 	sgbPlrTalkTbl = PANEL_HEIGHT + 16;
2207 	force_redraw = 255;
2208 	sgbTalkSavePos = sgbNextTalkSave;
2209 }
2210 
control_reset_talk()2211 void control_reset_talk()
2212 {
2213 	talkflag = FALSE;
2214 	sgbPlrTalkTbl = 0;
2215 	force_redraw = 255;
2216 }
2217 
control_press_enter()2218 static void control_press_enter()
2219 {
2220 	int i;
2221 	BYTE talk_save;
2222 
2223 	if (sgszTalkMsg[0] != 0) {
2224 		control_reset_talk_msg(sgszTalkMsg);
2225 		for (i = 0; i < 8; i++) {
2226 			if (!strcmp(sgszTalkSave[i], sgszTalkMsg))
2227 				break;
2228 		}
2229 		if (i >= 8) {
2230 			strcpy(sgszTalkSave[sgbNextTalkSave], sgszTalkMsg);
2231 			sgbNextTalkSave++;
2232 			sgbNextTalkSave &= 7;
2233 		} else {
2234 			talk_save = sgbNextTalkSave - 1;
2235 			talk_save &= 7;
2236 			if (i != talk_save) {
2237 				strcpy(sgszTalkSave[i], sgszTalkSave[talk_save]);
2238 				strcpy(sgszTalkSave[talk_save], sgszTalkMsg);
2239 			}
2240 		}
2241 		sgszTalkMsg[0] = '\0';
2242 		sgbTalkSavePos = sgbNextTalkSave;
2243 	}
2244 	control_reset_talk();
2245 }
2246 
control_talk_last_key(int vkey)2247 BOOL control_talk_last_key(int vkey)
2248 {
2249 	int result;
2250 
2251 	if (!gbIsMultiplayer)
2252 		return FALSE;
2253 
2254 	if (!talkflag)
2255 		return FALSE;
2256 
2257 	if ((DWORD)vkey < DVL_VK_SPACE)
2258 		return FALSE;
2259 
2260 	result = strlen(sgszTalkMsg);
2261 	if (result < 78) {
2262 		sgszTalkMsg[result] = vkey;
2263 		sgszTalkMsg[result + 1] = '\0';
2264 	}
2265 	return TRUE;
2266 }
2267 
control_up_down(int v)2268 static void control_up_down(int v)
2269 {
2270 	int i;
2271 
2272 	for (i = 0; i < 8; i++) {
2273 		sgbTalkSavePos = (v + sgbTalkSavePos) & 7;
2274 		if (sgszTalkSave[sgbTalkSavePos][0]) {
2275 			strcpy(sgszTalkMsg, sgszTalkSave[sgbTalkSavePos]);
2276 			return;
2277 		}
2278 	}
2279 }
2280 
control_presskeys(int vkey)2281 BOOL control_presskeys(int vkey)
2282 {
2283 	int len;
2284 	BOOL ret;
2285 
2286 	if (gbIsMultiplayer) {
2287 		if (!talkflag) {
2288 			ret = FALSE;
2289 		} else {
2290 			if (vkey == DVL_VK_SPACE) {
2291 			} else if (vkey == DVL_VK_ESCAPE) {
2292 				control_reset_talk();
2293 			} else if (vkey == DVL_VK_RETURN) {
2294 				control_press_enter();
2295 			} else if (vkey == DVL_VK_BACK) {
2296 				len = strlen(sgszTalkMsg);
2297 				if (len > 0)
2298 					sgszTalkMsg[len - 1] = '\0';
2299 			} else if (vkey == DVL_VK_DOWN) {
2300 				control_up_down(1);
2301 			} else if (vkey == DVL_VK_UP) {
2302 				control_up_down(-1);
2303 			} else {
2304 				return FALSE;
2305 			}
2306 			ret = TRUE;
2307 		}
2308 	} else {
2309 		ret = FALSE;
2310 	}
2311 	return ret;
2312 }
2313 
2314 DEVILUTION_END_NAMESPACE
2315