1 /**
2  * @file plrmsg.cpp
3  *
4  * Implementation of functionality for rendering the dungeons, monsters and calling other render routines.
5  */
6 #include "all.h"
7 
8 DEVILUTION_BEGIN_NAMESPACE
9 
10 /**
11  * Specifies the current light entry.
12  */
13 int light_table_index;
14 DWORD sgdwCursWdtOld;
15 DWORD sgdwCursX;
16 DWORD sgdwCursY;
17 /**
18  * Lower bound of back buffer.
19  */
20 DWORD sgdwCursHgt;
21 
22 /**
23  * Specifies the current MIN block of the level CEL file, as used during rendering of the level tiles.
24  *
25  * frameNum  := block & 0x0FFF
26  * frameType := block & 0x7000 >> 12
27  */
28 DWORD level_cel_block;
29 DWORD sgdwCursXOld;
30 DWORD sgdwCursYOld;
31 BOOLEAN AutoMapShowItems;
32 /**
33  * Specifies the type of arches to render.
34  */
35 char arch_draw_type;
36 /**
37  * Specifies whether transparency is active for the current CEL file being decoded.
38  */
39 int cel_transparency_active;
40 /**
41  * Specifies whether foliage (tile has extra content that overlaps previous tile) being rendered.
42  */
43 int cel_foliage_active = false;
44 /**
45  * Specifies the current dungeon piece ID of the level, as used during rendering of the level tiles.
46  */
47 int level_piece_id;
48 DWORD sgdwCursWdt;
49 void (*DrawPlrProc)(int, int, int, int, int, BYTE *, int, int, int, int);
50 BYTE sgSaveBack[8192];
51 DWORD sgdwCursHgtOld;
52 
53 bool dRendered[MAXDUNX][MAXDUNY];
54 
55 int frames;
56 BOOL frameflag;
57 int frameend;
58 int framerate;
59 int framestart;
60 
61 /* data */
62 
63 const char *const szMonModeAssert[] = {
64 	"standing",
65 	"walking (1)",
66 	"walking (2)",
67 	"walking (3)",
68 	"attacking",
69 	"getting hit",
70 	"dying",
71 	"attacking (special)",
72 	"fading in",
73 	"fading out",
74 	"attacking (ranged)",
75 	"standing (special)",
76 	"attacking (special ranged)",
77 	"delaying",
78 	"charging",
79 	"stoned",
80 	"healing",
81 	"talking"
82 };
83 
84 const char *const szPlrModeAssert[] = {
85 	"standing",
86 	"walking (1)",
87 	"walking (2)",
88 	"walking (3)",
89 	"attacking (melee)",
90 	"attacking (ranged)",
91 	"blocking",
92 	"getting hit",
93 	"dying",
94 	"casting a spell",
95 	"changing levels",
96 	"quitting"
97 };
98 
99 /**
100  * @brief Clear cursor state
101  */
ClearCursor()102 void ClearCursor() // CODE_FIX: this was supposed to be in cursor.cpp
103 {
104 	sgdwCursWdt = 0;
105 	sgdwCursWdtOld = 0;
106 }
107 
BlitCursor(BYTE * dst,int dst_pitch,BYTE * src,int src_pitch)108 static void BlitCursor(BYTE *dst, int dst_pitch, BYTE *src, int src_pitch)
109 {
110 	const int h = std::min(sgdwCursY + 1, sgdwCursHgt);
111 	for (int i = 0; i < h; ++i, src += src_pitch, dst += dst_pitch) {
112 		memcpy(dst, src, sgdwCursWdt);
113 	}
114 }
115 
116 /**
117  * @brief Remove the cursor from the buffer
118  */
scrollrt_draw_cursor_back_buffer(CelOutputBuffer out)119 static void scrollrt_draw_cursor_back_buffer(CelOutputBuffer out)
120 {
121 	if (sgdwCursWdt == 0) {
122 		return;
123 	}
124 
125 	BlitCursor(out.at(sgdwCursX, sgdwCursY), out.pitch(), sgSaveBack, sgdwCursWdt);
126 
127 	sgdwCursXOld = sgdwCursX;
128 	sgdwCursYOld = sgdwCursY;
129 	sgdwCursWdtOld = sgdwCursWdt;
130 	sgdwCursHgtOld = sgdwCursHgt;
131 	sgdwCursWdt = 0;
132 }
133 
134 /**
135  * @brief Draw the cursor on the given buffer
136  */
scrollrt_draw_cursor_item(CelOutputBuffer out)137 static void scrollrt_draw_cursor_item(CelOutputBuffer out)
138 {
139 	int i, mx, my;
140 	BYTE col;
141 
142 	assert(!sgdwCursWdt);
143 
144 	if (pcurs <= CURSOR_NONE || cursW == 0 || cursH == 0) {
145 		return;
146 	}
147 
148 	if (sgbControllerActive && !IsMovingMouseCursorWithController() && pcurs != CURSOR_TELEPORT && !invflag && (!chrflag || plr[myplr]._pStatPts <= 0)) {
149 		return;
150 	}
151 
152 	mx = MouseX - 1;
153 	if (mx < 0 - cursW - 1) {
154 		return;
155 	} else if (mx > gnScreenWidth - 1) {
156 		return;
157 	}
158 	my = MouseY - 1;
159 	if (my < 0 - cursH - 1) {
160 		return;
161 	} else if (my > gnScreenHeight - 1) {
162 		return;
163 	}
164 
165 	sgdwCursX = mx;
166 	sgdwCursWdt = sgdwCursX + cursW + 1;
167 	if (sgdwCursWdt > gnScreenWidth - 1) {
168 		sgdwCursWdt = gnScreenWidth - 1;
169 	}
170 	sgdwCursX &= ~3;
171 	sgdwCursWdt |= 3;
172 	sgdwCursWdt -= sgdwCursX;
173 	sgdwCursWdt++;
174 
175 	sgdwCursY = my;
176 	sgdwCursHgt = sgdwCursY + cursH + 1;
177 	if (sgdwCursHgt > gnScreenHeight - 1) {
178 		sgdwCursHgt = gnScreenHeight - 1;
179 	}
180 	sgdwCursHgt -= sgdwCursY;
181 	sgdwCursHgt++;
182 
183 	BlitCursor(sgSaveBack, sgdwCursWdt, out.at(sgdwCursX, sgdwCursY), out.pitch());
184 
185 	mx++;
186 	my++;
187 
188 	out = out.subregion(0, 0, out.w() - 2, out.h());
189 	if (pcurs >= CURSOR_FIRSTITEM) {
190 		col = PAL16_YELLOW + 5;
191 		if (plr[myplr].HoldItem._iMagical != 0) {
192 			col = PAL16_BLUE + 5;
193 		}
194 		if (!plr[myplr].HoldItem._iStatFlag) {
195 			col = PAL16_RED + 5;
196 		}
197 		if (pcurs <= 179) {
198 			CelBlitOutlineTo(out, col, mx, my + cursH - 1, pCursCels, pcurs, cursW, false);
199 			if (col != PAL16_RED + 5) {
200 				CelClippedDrawSafeTo(out, mx, my + cursH - 1, pCursCels, pcurs, cursW);
201 			} else {
202 				CelDrawLightRedSafeTo(out, mx, my + cursH - 1, pCursCels, pcurs, cursW, 1);
203 			}
204 		} else {
205 			CelBlitOutlineTo(out, col, mx, my + cursH - 1, pCursCels2, pcurs - 179, cursW, false);
206 			if (col != PAL16_RED + 5) {
207 				CelClippedDrawSafeTo(out, mx, my + cursH - 1, pCursCels2, pcurs - 179, cursW);
208 			} else {
209 				CelDrawLightRedSafeTo(out, mx, my + cursH - 1, pCursCels2, pcurs - 179, cursW, 0);
210 			}
211 		}
212 	} else {
213 		CelClippedDrawSafeTo(out, mx, my + cursH - 1, pCursCels, pcurs, cursW);
214 	}
215 }
216 
217 /**
218  * @brief Render a missile sprite
219  * @param out Output buffer
220  * @param m Pointer to MissileStruct struct
221  * @param sx Output buffer coordinate
222  * @param sy Output buffer coordinate
223  * @param pre Is the sprite in the background
224  */
DrawMissilePrivate(CelOutputBuffer out,MissileStruct * m,int sx,int sy,BOOL pre)225 void DrawMissilePrivate(CelOutputBuffer out, MissileStruct *m, int sx, int sy, BOOL pre)
226 {
227 	if (m->_miPreFlag != pre || !m->_miDrawFlag)
228 		return;
229 
230 	BYTE *pCelBuff = m->_miAnimData;
231 	if (pCelBuff == NULL) {
232 		SDL_Log("Draw Missile 2 type %d: NULL Cel Buffer", m->_mitype);
233 		return;
234 	}
235 	int nCel = m->_miAnimFrame;
236 	int frames = SDL_SwapLE32(*(DWORD *)pCelBuff);
237 	if (nCel < 1 || frames > 50 || nCel > frames) {
238 		SDL_Log("Draw Missile 2: frame %d of %d, missile type==%d", nCel, frames, m->_mitype);
239 		return;
240 	}
241 	int mx = sx + m->_mixoff - m->_miAnimWidth2;
242 	int my = sy + m->_miyoff;
243 	if (m->_miUniqTrans)
244 		Cl2DrawLightTbl(out, mx, my, m->_miAnimData, m->_miAnimFrame, m->_miAnimWidth, m->_miUniqTrans + 3);
245 	else if (m->_miLightFlag)
246 		Cl2DrawLight(out, mx, my, m->_miAnimData, m->_miAnimFrame, m->_miAnimWidth);
247 	else
248 		Cl2Draw(out, mx, my, m->_miAnimData, m->_miAnimFrame, m->_miAnimWidth);
249 }
250 
251 /**
252  * @brief Render a missile sprites for a given tile
253  * @param out Output buffer
254  * @param x dPiece coordinate
255  * @param y dPiece coordinate
256  * @param sx Output buffer coordinate
257  * @param sy Output buffer coordinate
258  * @param pre Is the sprite in the background
259  */
DrawMissile(CelOutputBuffer out,int x,int y,int sx,int sy,BOOL pre)260 void DrawMissile(CelOutputBuffer out, int x, int y, int sx, int sy, BOOL pre)
261 {
262 	int i;
263 	MissileStruct *m;
264 
265 	if (!(dFlags[x][y] & BFLAG_MISSILE))
266 		return;
267 
268 	if (dMissile[x][y] != -1) {
269 		m = &missile[dMissile[x][y] - 1];
270 		DrawMissilePrivate(out, m, sx, sy, pre);
271 		return;
272 	}
273 
274 	for (i = 0; i < nummissiles; i++) {
275 		assert(missileactive[i] < MAXMISSILES);
276 		m = &missile[missileactive[i]];
277 		if (m->_mix != x || m->_miy != y)
278 			continue;
279 		DrawMissilePrivate(out, m, sx, sy, pre);
280 	}
281 }
282 
283 /**
284  * @brief Render a monster sprite
285  * @param out Output buffer
286  * @param x dPiece coordinate
287  * @param y dPiece coordinate
288  * @param mx Output buffer coordinate
289  * @param my Output buffer coordinate
290  * @param m Id of monster
291  */
DrawMonster(CelOutputBuffer out,int x,int y,int mx,int my,int m)292 static void DrawMonster(CelOutputBuffer out, int x, int y, int mx, int my, int m)
293 {
294 	if (m < 0 || m >= MAXMONSTERS) {
295 		SDL_Log("Draw Monster: tried to draw illegal monster %d", m);
296 		return;
297 	}
298 
299 	BYTE *pCelBuff = monster[m]._mAnimData;
300 	if (pCelBuff == NULL) {
301 		SDL_Log("Draw Monster \"%s\": NULL Cel Buffer", monster[m].mName);
302 		return;
303 	}
304 
305 	int nCel = monster[m]._mAnimFrame;
306 	int frames = SDL_SwapLE32(*(DWORD *)pCelBuff);
307 	if (nCel < 1 || frames > 50 || nCel > frames) {
308 		const char *szMode = "unknown action";
309 		if (monster[m]._mmode <= 17)
310 			szMode = szMonModeAssert[monster[m]._mmode];
311 		SDL_Log(
312 		    "Draw Monster \"%s\" %s: facing %d, frame %d of %d",
313 		    monster[m].mName,
314 		    szMode,
315 		    monster[m]._mdir,
316 		    nCel,
317 		    frames);
318 		return;
319 	}
320 
321 	if (!(dFlags[x][y] & BFLAG_LIT)) {
322 		Cl2DrawLightTbl(out, mx, my, monster[m]._mAnimData, monster[m]._mAnimFrame, monster[m].MType->width, 1);
323 		return;
324 	}
325 
326 	char trans = 0;
327 	if (monster[m]._uniqtype)
328 		trans = monster[m]._uniqtrans + 4;
329 	if (monster[m]._mmode == MM_STONE)
330 		trans = 2;
331 	if (plr[myplr]._pInfraFlag && light_table_index > 8)
332 		trans = 1;
333 	if (trans)
334 		Cl2DrawLightTbl(out, mx, my, monster[m]._mAnimData, monster[m]._mAnimFrame, monster[m].MType->width, trans);
335 	else
336 		Cl2DrawLight(out, mx, my, monster[m]._mAnimData, monster[m]._mAnimFrame, monster[m].MType->width);
337 }
338 
339 /**
340  * @brief Helper for rendering player a Mana Shield
341  * @param out Output buffer
342  * @param pnum Player id
343  * @param sx Output buffer coordinate
344  * @param sy Output buffer coordinate
345  * @param lighting Should lighting be applied
346  */
DrawManaShield(CelOutputBuffer out,int pnum,int x,int y,bool lighting)347 static void DrawManaShield(CelOutputBuffer out, int pnum, int x, int y, bool lighting)
348 {
349 	if (!plr[pnum].pManaShield)
350 		return;
351 
352 	x += plr[pnum]._pAnimWidth2 - misfiledata[MFILE_MANASHLD].mAnimWidth2[0];
353 
354 	int width = misfiledata[MFILE_MANASHLD].mAnimWidth[0];
355 	BYTE *pCelBuff = misfiledata[MFILE_MANASHLD].mAnimData[0];
356 
357 	if (pnum == myplr) {
358 		Cl2Draw(out, x, y, pCelBuff, 1, width);
359 		return;
360 	}
361 
362 	if (lighting) {
363 		Cl2DrawLightTbl(out, x, y, pCelBuff, 1, width, 1);
364 		return;
365 	}
366 
367 	Cl2DrawLight(out, x, y, pCelBuff, 1, width);
368 }
369 
370 /**
371  * @brief Render a player sprite
372  * @param out Output buffer
373  * @param pnum Player id
374  * @param x dPiece coordinate
375  * @param y dPiece coordinate
376  * @param px Output buffer coordinate
377  * @param py Output buffer coordinate
378  * @param pCelBuff sprite buffer
379  * @param nCel frame
380  * @param nWidth width
381  */
DrawPlayer(CelOutputBuffer out,int pnum,int x,int y,int px,int py,BYTE * pCelBuff,int nCel,int nWidth)382 static void DrawPlayer(CelOutputBuffer out, int pnum, int x, int y, int px, int py, BYTE *pCelBuff, int nCel, int nWidth)
383 {
384 	if ((dFlags[x][y] & BFLAG_LIT) == 0 && !plr[myplr]._pInfraFlag && leveltype != DTYPE_TOWN) {
385 		return;
386 	}
387 
388 	if (pCelBuff == NULL) {
389 		SDL_Log("Drawing player %d \"%s\": NULL Cel Buffer", pnum, plr[pnum]._pName);
390 		return;
391 	}
392 
393 	int frames = SDL_SwapLE32(*(DWORD *)pCelBuff);
394 	if (nCel < 1 || frames > 50 || nCel > frames) {
395 		const char *szMode = "unknown action";
396 		if (plr[pnum]._pmode <= PM_QUIT)
397 			szMode = szPlrModeAssert[plr[pnum]._pmode];
398 		SDL_Log(
399 		    "Drawing player %d \"%s\" %s: facing %d, frame %d of %d",
400 		    pnum,
401 		    plr[pnum]._pName,
402 		    szMode,
403 		    plr[pnum]._pdir,
404 		    nCel,
405 		    frames);
406 		return;
407 	}
408 
409 	if (pnum == pcursplr)
410 		Cl2DrawOutline(out, 165, px, py, pCelBuff, nCel, nWidth);
411 
412 	if (pnum == myplr) {
413 		Cl2Draw(out, px, py, pCelBuff, nCel, nWidth);
414 		DrawManaShield(out, pnum, px, py, true);
415 		return;
416 	}
417 
418 	if (!(dFlags[x][y] & BFLAG_LIT) || (plr[myplr]._pInfraFlag && light_table_index > 8)) {
419 		Cl2DrawLightTbl(out, px, py, pCelBuff, nCel, nWidth, 1);
420 		DrawManaShield(out, pnum, px, py, true);
421 		return;
422 	}
423 
424 	int l = light_table_index;
425 	if (light_table_index < 5)
426 		light_table_index = 0;
427 	else
428 		light_table_index -= 5;
429 
430 	Cl2DrawLight(out, px, py, pCelBuff, nCel, nWidth);
431 	DrawManaShield(out, pnum, px, py, false);
432 
433 	light_table_index = l;
434 }
435 
436 /**
437  * @brief Render a player sprite
438  * @param out Output buffer
439  * @param x dPiece coordinate
440  * @param y dPiece coordinate
441  * @param sx Output buffer coordinate
442  * @param sy Output buffer coordinate
443  */
DrawDeadPlayer(CelOutputBuffer out,int x,int y,int sx,int sy)444 void DrawDeadPlayer(CelOutputBuffer out, int x, int y, int sx, int sy)
445 {
446 	int i, px, py;
447 	PlayerStruct *p;
448 
449 	dFlags[x][y] &= ~BFLAG_DEAD_PLAYER;
450 
451 	for (i = 0; i < MAX_PLRS; i++) {
452 		p = &plr[i];
453 		if (p->plractive && p->_pHitPoints == 0 && p->plrlevel == (BYTE)currlevel && p->_px == x && p->_py == y) {
454 			dFlags[x][y] |= BFLAG_DEAD_PLAYER;
455 			px = sx + p->_pxoff - p->_pAnimWidth2;
456 			py = sy + p->_pyoff;
457 			DrawPlayer(out, i, x, y, px, py, p->_pAnimData, p->_pAnimFrame, p->_pAnimWidth);
458 		}
459 	}
460 }
461 
462 /**
463  * @brief Render an object sprite
464  * @param out Output buffer
465  * @param x dPiece coordinate
466  * @param y dPiece coordinate
467  * @param ox Output buffer coordinate
468  * @param oy Output buffer coordinate
469  * @param pre Is the sprite in the background
470  */
DrawObject(CelOutputBuffer out,int x,int y,int ox,int oy,BOOL pre)471 static void DrawObject(CelOutputBuffer out, int x, int y, int ox, int oy, BOOL pre)
472 {
473 	if (dObject[x][y] == 0 || light_table_index >= lightmax)
474 		return;
475 
476 	int sx, sy;
477 	char bv;
478 	if (dObject[x][y] > 0) {
479 		bv = dObject[x][y] - 1;
480 		if (object[bv]._oPreFlag != pre)
481 			return;
482 		sx = ox - object[bv]._oAnimWidth2;
483 		sy = oy;
484 	} else {
485 		bv = -(dObject[x][y] + 1);
486 		if (object[bv]._oPreFlag != pre)
487 			return;
488 		int xx = object[bv]._ox - x;
489 		int yy = object[bv]._oy - y;
490 		sx = (xx << 5) + ox - object[bv]._oAnimWidth2 - (yy << 5);
491 		sy = oy + (yy << 4) + (xx << 4);
492 	}
493 
494 	assert(bv >= 0 && bv < MAXOBJECTS);
495 
496 	BYTE *pCelBuff = object[bv]._oAnimData;
497 	if (pCelBuff == NULL) {
498 		SDL_Log("Draw Object type %d: NULL Cel Buffer", object[bv]._otype);
499 		return;
500 	}
501 
502 	int nCel = object[bv]._oAnimFrame;
503 	int frames = SDL_SwapLE32(*(DWORD *)pCelBuff);
504 	if (nCel < 1 || frames > 50 || nCel > frames) {
505 		SDL_Log("Draw Object: frame %d of %d, object type==%d", nCel, frames, object[bv]._otype);
506 		return;
507 	}
508 
509 	if (bv == pcursobj)
510 		CelBlitOutlineTo(out, 194, sx, sy, object[bv]._oAnimData, object[bv]._oAnimFrame, object[bv]._oAnimWidth);
511 	if (object[bv]._oLight) {
512 		CelClippedDrawLightTo(out, sx, sy, object[bv]._oAnimData, object[bv]._oAnimFrame, object[bv]._oAnimWidth);
513 	} else {
514 		CelClippedDrawTo(out, sx, sy, object[bv]._oAnimData, object[bv]._oAnimFrame, object[bv]._oAnimWidth);
515 	}
516 }
517 
518 static void scrollrt_draw_dungeon(CelOutputBuffer, int, int, int, int);
519 
520 /**
521  * @brief Render a cell
522  * @param out Target buffer
523  * @param x dPiece coordinate
524  * @param y dPiece coordinate
525  * @param sx Target buffer coordinate
526  * @param sy Target buffer coordinate
527  */
drawCell(CelOutputBuffer out,int x,int y,int sx,int sy)528 static void drawCell(CelOutputBuffer out, int x, int y, int sx, int sy)
529 {
530 	MICROS *pMap = &dpiece_defs_map_2[x][y];
531 	level_piece_id = dPiece[x][y];
532 	cel_transparency_active = (BYTE)(nTransTable[level_piece_id] & TransList[dTransVal[x][y]]);
533 	cel_foliage_active = !nSolidTable[level_piece_id];
534 	for (int i = 0; i < (MicroTileLen >> 1); i++) {
535 		level_cel_block = pMap->mt[2 * i];
536 		if (level_cel_block != 0) {
537 			arch_draw_type = i == 0 ? 1 : 0;
538 			RenderTile(out, sx, sy);
539 		}
540 		level_cel_block = pMap->mt[2 * i + 1];
541 		if (level_cel_block != 0) {
542 			arch_draw_type = i == 0 ? 2 : 0;
543 			RenderTile(out, sx + TILE_WIDTH / 2, sy);
544 		}
545 		sy -= TILE_HEIGHT;
546 	}
547 	cel_foliage_active = false;
548 }
549 
550 /**
551  * @brief Render a floor tiles
552  * @param out Target buffer
553  * @param x dPiece coordinate
554  * @param y dPiece coordinate
555  * @param sx Target buffer coordinate
556  * @param sy Target buffer coordinate
557  */
drawFloor(CelOutputBuffer out,int x,int y,int sx,int sy)558 static void drawFloor(CelOutputBuffer out, int x, int y, int sx, int sy)
559 {
560 	cel_transparency_active = 0;
561 	light_table_index = dLight[x][y];
562 
563 	arch_draw_type = 1; // Left
564 	level_cel_block = dpiece_defs_map_2[x][y].mt[0];
565 	if (level_cel_block != 0) {
566 		RenderTile(out, sx, sy);
567 	}
568 	arch_draw_type = 2; // Right
569 	level_cel_block = dpiece_defs_map_2[x][y].mt[1];
570 	if (level_cel_block != 0) {
571 		RenderTile(out, sx + TILE_WIDTH / 2, sy);
572 	}
573 }
574 
575 /**
576  * @brief Draw item for a given tile
577  * @param out Output buffer
578  * @param y dPiece coordinate
579  * @param x dPiece coordinate
580  * @param sx Output buffer coordinate
581  * @param sy Output buffer coordinate
582  * @param pre Is the sprite in the background
583  */
DrawItem(CelOutputBuffer out,int x,int y,int sx,int sy,BOOL pre)584 static void DrawItem(CelOutputBuffer out, int x, int y, int sx, int sy, BOOL pre)
585 {
586 	char bItem = dItem[x][y];
587 
588 	assert((unsigned char)bItem <= MAXITEMS);
589 
590 	if (bItem > MAXITEMS || bItem <= 0)
591 		return;
592 
593 	ItemStruct *pItem = &item[bItem - 1];
594 	if (pItem->_iPostDraw == pre)
595 		return;
596 
597 	BYTE *pCelBuff = pItem->_iAnimData;
598 	if (pCelBuff == NULL) {
599 		SDL_Log("Draw Item \"%s\" 1: NULL Cel Buffer", pItem->_iIName);
600 		return;
601 	}
602 
603 	int nCel = pItem->_iAnimFrame;
604 	int frames = SDL_SwapLE32(*(DWORD *)pCelBuff);
605 	if (nCel < 1 || frames > 50 || nCel > frames) {
606 		SDL_Log("Draw \"%s\" Item 1: frame %d of %d, item type==%d", pItem->_iIName, nCel, frames, pItem->_itype);
607 		return;
608 	}
609 
610 	int px = sx - pItem->_iAnimWidth2;
611 	if (bItem - 1 == pcursitem || AutoMapShowItems) {
612 		CelBlitOutlineTo(out, 181, px, sy, pCelBuff, nCel, pItem->_iAnimWidth);
613 	}
614 	CelClippedDrawLightTo(out, px, sy, pCelBuff, nCel, pItem->_iAnimWidth);
615 }
616 
617 /**
618  * @brief Check if and how a monster should be rendered
619  * @param out Output buffer
620  * @param y dPiece coordinate
621  * @param x dPiece coordinate
622  * @param oy dPiece Y offset
623  * @param sx Output buffer coordinate
624  * @param sy Output buffer coordinate
625  */
DrawMonsterHelper(CelOutputBuffer out,int x,int y,int oy,int sx,int sy)626 static void DrawMonsterHelper(CelOutputBuffer out, int x, int y, int oy, int sx, int sy)
627 {
628 	int mi, px, py;
629 	MonsterStruct *pMonster;
630 
631 	mi = dMonster[x][y + oy];
632 	mi = mi > 0 ? mi - 1 : -(mi + 1);
633 
634 	if (leveltype == DTYPE_TOWN) {
635 		px = sx - towner[mi]._tAnimWidth2;
636 		if (mi == pcursmonst) {
637 			CelBlitOutlineTo(out, 166, px, sy, towner[mi]._tAnimData, towner[mi]._tAnimFrame, towner[mi]._tAnimWidth);
638 		}
639 		assert(towner[mi]._tAnimData);
640 		CelClippedDrawTo(out, px, sy, towner[mi]._tAnimData, towner[mi]._tAnimFrame, towner[mi]._tAnimWidth);
641 		return;
642 	}
643 
644 	if (!(dFlags[x][y] & BFLAG_LIT) && !plr[myplr]._pInfraFlag)
645 		return;
646 
647 	if (mi < 0 || mi >= MAXMONSTERS) {
648 		SDL_Log("Draw Monster: tried to draw illegal monster %d", mi);
649 		return;
650 	}
651 
652 	pMonster = &monster[mi];
653 	if (pMonster->_mFlags & MFLAG_HIDDEN) {
654 		return;
655 	}
656 
657 	if (pMonster->MType == NULL) {
658 		SDL_Log("Draw Monster \"%s\": uninitialized monster", pMonster->mName);
659 		return;
660 	}
661 
662 	px = sx + pMonster->_mxoff - pMonster->MType->width2;
663 	py = sy + pMonster->_myoff;
664 	if (mi == pcursmonst) {
665 		Cl2DrawOutline(out, 233, px, py, pMonster->_mAnimData, pMonster->_mAnimFrame, pMonster->MType->width);
666 	}
667 	DrawMonster(out, x, y, px, py, mi);
668 }
669 
670 /**
671  * @brief Check if and how a player should be rendered
672  * @param out Output buffer
673  * @param y dPiece coordinate
674  * @param x dPiece coordinate
675  * @param sx Output buffer coordinate
676  * @param sy Output buffer coordinate
677  */
DrawPlayerHelper(CelOutputBuffer out,int x,int y,int sx,int sy)678 static void DrawPlayerHelper(CelOutputBuffer out, int x, int y, int sx, int sy)
679 {
680 	int p = dPlayer[x][y];
681 	p = p > 0 ? p - 1 : -(p + 1);
682 
683 	if (p < 0 || p >= MAX_PLRS) {
684 		SDL_Log("draw player: tried to draw illegal player %d", p);
685 		return;
686 	}
687 
688 	PlayerStruct *pPlayer = &plr[p];
689 	int px = sx + pPlayer->_pxoff - pPlayer->_pAnimWidth2;
690 	int py = sy + pPlayer->_pyoff;
691 
692 	DrawPlayer(out, p, x, y, px, py, pPlayer->_pAnimData, pPlayer->_pAnimFrame, pPlayer->_pAnimWidth);
693 }
694 
695 /**
696  * @brief Render object sprites
697  * @param out Target buffer
698  * @param sx dPiece coordinate
699  * @param sy dPiece coordinate
700  * @param dx Target buffer coordinate
701  * @param dy Target buffer coordinate
702  */
scrollrt_draw_dungeon(CelOutputBuffer out,int sx,int sy,int dx,int dy)703 static void scrollrt_draw_dungeon(CelOutputBuffer out, int sx, int sy, int dx, int dy)
704 {
705 	assert((DWORD)sx < MAXDUNX);
706 	assert((DWORD)sy < MAXDUNY);
707 
708 	if (dRendered[sx][sy])
709 		return;
710 	dRendered[sx][sy] = true;
711 
712 	light_table_index = dLight[sx][sy];
713 
714 	drawCell(out, sx, sy, dx, dy);
715 
716 	char bFlag = dFlags[sx][sy];
717 	char bDead = dDead[sx][sy];
718 	char bMap = dTransVal[sx][sy];
719 
720 	int negMon = 0;
721 	if (sy > 0) // check for OOB
722 		negMon = dMonster[sx][sy - 1];
723 
724 #ifdef _DEBUG
725 	if (visiondebug && bFlag & BFLAG_LIT) {
726 		CelClippedDrawTo(out, dx, dy, pSquareCel, 1, 64);
727 	}
728 #endif
729 
730 	if (MissilePreFlag) {
731 		DrawMissile(out, sx, sy, dx, dy, TRUE);
732 	}
733 
734 	if (light_table_index < lightmax && bDead != 0) {
735 		do {
736 			DeadStruct *pDeadGuy = &dead[(bDead & 0x1F) - 1];
737 			char dd = (bDead >> 5) & 7;
738 			int px = dx - pDeadGuy->_deadWidth2;
739 			BYTE *pCelBuff = pDeadGuy->_deadData[dd];
740 			assert(pCelBuff != NULL);
741 			if (pCelBuff == NULL)
742 				break;
743 			int frames = SDL_SwapLE32(*(DWORD *)pCelBuff);
744 			int nCel = pDeadGuy->_deadFrame;
745 			if (nCel < 1 || frames > 50 || nCel > frames) {
746 				SDL_Log("Unclipped dead: frame %d of %d, deadnum==%d", nCel, frames, (bDead & 0x1F) - 1);
747 				break;
748 			}
749 			if (pDeadGuy->_deadtrans != 0) {
750 				Cl2DrawLightTbl(out, px, dy, pCelBuff, nCel, pDeadGuy->_deadWidth, pDeadGuy->_deadtrans);
751 			} else {
752 				Cl2DrawLight(out, px, dy, pCelBuff, nCel, pDeadGuy->_deadWidth);
753 			}
754 		} while (0);
755 	}
756 	DrawObject(out, sx, sy, dx, dy, 1);
757 	DrawItem(out, sx, sy, dx, dy, 1);
758 	if (bFlag & BFLAG_PLAYERLR) {
759 		assert((DWORD)(sy - 1) < MAXDUNY);
760 		DrawPlayerHelper(out, sx, sy - 1, dx, dy);
761 	}
762 	if (bFlag & BFLAG_MONSTLR && negMon < 0) {
763 		DrawMonsterHelper(out, sx, sy, -1, dx, dy);
764 	}
765 	if (bFlag & BFLAG_DEAD_PLAYER) {
766 		DrawDeadPlayer(out, sx, sy, dx, dy);
767 	}
768 	if (dPlayer[sx][sy] > 0) {
769 		DrawPlayerHelper(out, sx, sy, dx, dy);
770 	}
771 	if (dMonster[sx][sy] > 0) {
772 		DrawMonsterHelper(out, sx, sy, 0, dx, dy);
773 	}
774 	DrawMissile(out, sx, sy, dx, dy, FALSE);
775 	DrawObject(out, sx, sy, dx, dy, 0);
776 	DrawItem(out, sx, sy, dx, dy, 0);
777 
778 	if (leveltype != DTYPE_TOWN) {
779 		char bArch = dSpecial[sx][sy];
780 		if (bArch != 0) {
781 			cel_transparency_active = TransList[bMap];
782 #ifdef _DEBUG
783 			if (GetAsyncKeyState(DVL_VK_MENU) & 0x8000) {
784 				cel_transparency_active = 0; // Turn transparency off here for debugging
785 			}
786 #endif
787 			CelClippedBlitLightTransTo(out, dx, dy, pSpecialCels, bArch, 64);
788 #ifdef _DEBUG
789 			if (GetAsyncKeyState(DVL_VK_MENU) & 0x8000) {
790 				cel_transparency_active = TransList[bMap]; // Turn transparency back to its normal state
791 			}
792 #endif
793 		}
794 	} else {
795 		// Tree leaves should always cover player when entering or leaving the tile,
796 		// So delay the rendering until after the next row is being drawn.
797 		// This could probably have been better solved by sprites in screen space.
798 		if (sx > 0 && sy > 0 && dy > TILE_HEIGHT) {
799 			char bArch = dSpecial[sx - 1][sy - 1];
800 			if (bArch != 0) {
801 				CelDrawTo(out, dx, dy - TILE_HEIGHT, pSpecialCels, bArch, 64);
802 			}
803 		}
804 	}
805 }
806 
807 /**
808  * @brief Render a row of tiles
809  * @param out Buffer to render to
810  * @param x dPiece coordinate
811  * @param y dPiece coordinate
812  * @param sx Target buffer coordinate
813  * @param sy Target buffer coordinate
814  * @param rows Number of rows
815  * @param columns Tile in a row
816  */
scrollrt_drawFloor(CelOutputBuffer out,int x,int y,int sx,int sy,int rows,int columns)817 static void scrollrt_drawFloor(CelOutputBuffer out, int x, int y, int sx, int sy, int rows, int columns)
818 {
819 	for (int i = 0; i < rows; i++) {
820 		for (int j = 0; j < columns; j++) {
821 			if (x >= 0 && x < MAXDUNX && y >= 0 && y < MAXDUNY) {
822 				level_piece_id = dPiece[x][y];
823 				if (level_piece_id != 0) {
824 					if (!nSolidTable[level_piece_id])
825 						drawFloor(out, x, y, sx, sy);
826 				} else {
827 					world_draw_black_tile(out, sx, sy);
828 				}
829 			} else {
830 				world_draw_black_tile(out, sx, sy);
831 			}
832 			ShiftGrid(&x, &y, 1, 0);
833 			sx += TILE_WIDTH;
834 		}
835 		// Return to start of row
836 		ShiftGrid(&x, &y, -columns, 0);
837 		sx -= columns * TILE_WIDTH;
838 
839 		// Jump to next row
840 		sy += TILE_HEIGHT / 2;
841 		if (i & 1) {
842 			x++;
843 			columns--;
844 			sx += TILE_WIDTH / 2;
845 		} else {
846 			y++;
847 			columns++;
848 			sx -= TILE_WIDTH / 2;
849 		}
850 	}
851 }
852 
853 #define IsWall(x, y) (dPiece[x][y] == 0 || nSolidTable[dPiece[x][y]] || dSpecial[x][y] != 0)
854 #define IsWalkable(x, y) (dPiece[x][y] != 0 && !nSolidTable[dPiece[x][y]])
855 
856 /**
857  * @brief Render a row of tile
858  * @param out Output buffer
859  * @param x dPiece coordinate
860  * @param y dPiece coordinate
861  * @param sx Buffer coordinate
862  * @param sy Buffer coordinate
863  * @param rows Number of rows
864  * @param columns Tile in a row
865  */
scrollrt_draw(CelOutputBuffer out,int x,int y,int sx,int sy,int rows,int columns)866 static void scrollrt_draw(CelOutputBuffer out, int x, int y, int sx, int sy, int rows, int columns)
867 {
868 	// Keep evaluating until MicroTiles can't affect screen
869 	rows += MicroTileLen;
870 	memset(dRendered, 0, sizeof(dRendered));
871 
872 	for (int i = 0; i < rows; i++) {
873 		for (int j = 0; j < columns; j++) {
874 			if (x >= 0 && x < MAXDUNX && y >= 0 && y < MAXDUNY) {
875 				if (x + 1 < MAXDUNX && y - 1 >= 0 && sx + TILE_WIDTH <= gnScreenWidth) {
876 					// Render objects behind walls first to prevent sprites, that are moving
877 					// between tiles, from poking through the walls as they exceed the tile bounds.
878 					// A proper fix for this would probably be to layout the sceen and render by
879 					// sprite screen position rather than tile position.
880 					if (IsWall(x, y) && (IsWall(x + 1, y) || (x > 0 && IsWall(x - 1, y)))) { // Part of a wall aligned on the x-axis
881 						if (IsWalkable(x + 1, y - 1) && IsWalkable(x, y - 1)) {              // Has walkable area behind it
882 							scrollrt_draw_dungeon(out, x + 1, y - 1, sx + TILE_WIDTH, sy);
883 						}
884 					}
885 				}
886 				if (dPiece[x][y] != 0) {
887 					scrollrt_draw_dungeon(out, x, y, sx, sy);
888 				}
889 			}
890 			ShiftGrid(&x, &y, 1, 0);
891 			sx += TILE_WIDTH;
892 		}
893 		// Return to start of row
894 		ShiftGrid(&x, &y, -columns, 0);
895 		sx -= columns * TILE_WIDTH;
896 
897 		// Jump to next row
898 		sy += TILE_HEIGHT / 2;
899 		if (i & 1) {
900 			x++;
901 			columns--;
902 			sx += TILE_WIDTH / 2;
903 		} else {
904 			y++;
905 			columns++;
906 			sx -= TILE_WIDTH / 2;
907 		}
908 	}
909 }
910 
911 /**
912  * @brief Scale up the rendered part of the back buffer to take up the full view
913  */
Zoom(CelOutputBuffer out)914 static void Zoom(CelOutputBuffer out)
915 {
916 	int wdt = gnScreenWidth / 2;
917 
918 	int src_x = gnScreenWidth / 2 - 1;
919 	int dst_x = gnScreenWidth - 1;
920 
921 	if (PANELS_COVER) {
922 		if (chrflag || questlog) {
923 			wdt >>= 1;
924 			src_x -= wdt;
925 		} else if (invflag || sbookflag) {
926 			wdt >>= 1;
927 			src_x -= wdt;
928 			dst_x -= SPANEL_WIDTH;
929 		}
930 	}
931 
932 	BYTE *src = out.at(src_x, gnViewportHeight / 2 - 1);
933 	BYTE *dst = out.at(dst_x, gnViewportHeight - 1);
934 
935 	for (int hgt = 0; hgt < gnViewportHeight / 2; hgt++) {
936 		for (int i = 0; i < wdt; i++) {
937 			*dst-- = *src;
938 			*dst-- = *src;
939 			src--;
940 		}
941 		memcpy(dst - out.pitch(), dst, wdt * 2 + 1);
942 		src -= out.pitch() - wdt;
943 		dst -= 2 * (out.pitch() - wdt);
944 	}
945 }
946 
947 /**
948  * @brief Shifting the view area along the logical grid
949  *        Note: this won't allow you to shift between even and odd rows
950  * @param horizontal Shift the screen left or right
951  * @param vertical Shift the screen up or down
952  */
ShiftGrid(int * x,int * y,int horizontal,int vertical)953 void ShiftGrid(int *x, int *y, int horizontal, int vertical)
954 {
955 	*x += vertical + horizontal;
956 	*y += vertical - horizontal;
957 }
958 
959 /**
960  * @brief Gets the number of rows covered by the main panel
961  */
RowsCoveredByPanel()962 int RowsCoveredByPanel()
963 {
964 	if (gnScreenWidth <= PANEL_WIDTH) {
965 		return 0;
966 	}
967 
968 	int rows = PANEL_HEIGHT / TILE_HEIGHT;
969 	if (!zoomflag) {
970 		rows /= 2;
971 	}
972 
973 	return rows;
974 }
975 
976 /**
977  * @brief Calculate the offset needed for centering tiles in view area
978  * @param offsetX Offset in pixels
979  * @param offsetY Offset in pixels
980  */
CalcTileOffset(int * offsetX,int * offsetY)981 void CalcTileOffset(int *offsetX, int *offsetY)
982 {
983 	int x, y;
984 
985 	if (zoomflag) {
986 		x = gnScreenWidth % TILE_WIDTH;
987 		y = gnViewportHeight % TILE_HEIGHT;
988 	} else {
989 		x = (gnScreenWidth / 2) % TILE_WIDTH;
990 		y = (gnViewportHeight / 2) % TILE_HEIGHT;
991 	}
992 
993 	if (x)
994 		x = (TILE_WIDTH - x) / 2;
995 	if (y)
996 		y = (TILE_HEIGHT - y) / 2;
997 
998 	*offsetX = x;
999 	*offsetY = y;
1000 }
1001 
1002 /**
1003  * @brief Calculate the needed diamond tile to cover the view area
1004  * @param columns Tiles needed per row
1005  * @param rows Both even and odd rows
1006  */
TilesInView(int * rcolumns,int * rrows)1007 void TilesInView(int *rcolumns, int *rrows)
1008 {
1009 	int columns = gnScreenWidth / TILE_WIDTH;
1010 	if (gnScreenWidth % TILE_WIDTH) {
1011 		columns++;
1012 	}
1013 	int rows = gnViewportHeight / TILE_HEIGHT;
1014 	if (gnViewportHeight % TILE_HEIGHT) {
1015 		rows++;
1016 	}
1017 
1018 	if (!zoomflag) {
1019 		// Half the number of tiles, rounded up
1020 		if (columns & 1) {
1021 			columns++;
1022 		}
1023 		columns /= 2;
1024 		if (rows & 1) {
1025 			rows++;
1026 		}
1027 		rows /= 2;
1028 	}
1029 
1030 	*rcolumns = columns;
1031 	*rrows = rows;
1032 }
1033 
1034 int tileOffsetX;
1035 int tileOffsetY;
1036 int tileShiftX;
1037 int tileShiftY;
1038 int tileColums;
1039 int tileRows;
1040 
CalcViewportGeometry()1041 void CalcViewportGeometry()
1042 {
1043 	int xo, yo;
1044 	tileShiftX = 0;
1045 	tileShiftY = 0;
1046 
1047 	// Adjust by player offset and tile grid alignment
1048 	CalcTileOffset(&xo, &yo);
1049 	tileOffsetX = 0 - xo;
1050 	tileOffsetY = 0 - yo - 1 + TILE_HEIGHT / 2;
1051 
1052 	TilesInView(&tileColums, &tileRows);
1053 	int lrow = tileRows - RowsCoveredByPanel();
1054 
1055 	// Center player tile on screen
1056 	ShiftGrid(&tileShiftX, &tileShiftY, -tileColums / 2, -lrow / 2);
1057 
1058 	tileRows *= 2;
1059 
1060 	// Align grid
1061 	if ((tileColums & 1) == 0) {
1062 		tileShiftY--; // Shift player row to one that can be centered with out pixel offset
1063 		if ((lrow & 1) == 0) {
1064 			// Offset tile to vertically align the player when both rows and colums are even
1065 			tileRows++;
1066 			tileOffsetY -= TILE_HEIGHT / 2;
1067 		}
1068 	} else if (tileColums & 1 && lrow & 1) {
1069 		// Offset tile to vertically align the player when both rows and colums are odd
1070 		ShiftGrid(&tileShiftX, &tileShiftY, 0, -1);
1071 		tileRows++;
1072 		tileOffsetY -= TILE_HEIGHT / 2;
1073 	}
1074 
1075 	// Slightly lower the zoomed view
1076 	if (!zoomflag) {
1077 		tileOffsetY += TILE_HEIGHT / 4;
1078 		if (yo < TILE_HEIGHT / 4)
1079 			tileRows++;
1080 	}
1081 
1082 	tileRows++; // Cover lower edge saw tooth, right edge accounted for in scrollrt_draw()
1083 }
1084 
1085 /**
1086  * @brief Configure render and process screen rows
1087  * @param full_out Buffer to render to
1088  * @param x Center of view in dPiece coordinate
1089  * @param y Center of view in dPiece coordinate
1090  */
DrawGame(CelOutputBuffer full_out,int x,int y)1091 static void DrawGame(CelOutputBuffer full_out, int x, int y)
1092 {
1093 	int sx, sy, columns, rows;
1094 
1095 	// Limit rendering to the view area
1096 	CelOutputBuffer out = zoomflag
1097 	    ? full_out.subregionY(0, gnViewportHeight)
1098 	    : full_out.subregionY(0, gnViewportHeight / 2);
1099 
1100 	// Adjust by player offset and tile grid alignment
1101 	sx = ScrollInfo._sxoff + tileOffsetX;
1102 	sy = ScrollInfo._syoff + tileOffsetY;
1103 
1104 	columns = tileColums;
1105 	rows = tileRows;
1106 
1107 	x += tileShiftX;
1108 	y += tileShiftY;
1109 
1110 	// Skip rendering parts covered by the panels
1111 	if (PANELS_COVER) {
1112 		if (zoomflag) {
1113 			if (chrflag || questlog) {
1114 				ShiftGrid(&x, &y, 2, 0);
1115 				columns -= 4;
1116 				sx += SPANEL_WIDTH - TILE_WIDTH / 2;
1117 			}
1118 			if (invflag || sbookflag) {
1119 				ShiftGrid(&x, &y, 2, 0);
1120 				columns -= 4;
1121 				sx += -TILE_WIDTH / 2;
1122 			}
1123 		} else {
1124 			if (chrflag || questlog) {
1125 				ShiftGrid(&x, &y, 1, 0);
1126 				columns -= 2;
1127 				sx += -TILE_WIDTH / 2 / 2; // SPANEL_WIDTH accounted for in Zoom()
1128 			}
1129 			if (invflag || sbookflag) {
1130 				ShiftGrid(&x, &y, 1, 0);
1131 				columns -= 2;
1132 				sx += -TILE_WIDTH / 2 / 2;
1133 			}
1134 		}
1135 	}
1136 
1137 	// Draw areas moving in and out of the screen
1138 	switch (ScrollInfo._sdir) {
1139 	case SDIR_N:
1140 		sy -= TILE_HEIGHT;
1141 		ShiftGrid(&x, &y, 0, -1);
1142 		rows += 2;
1143 		break;
1144 	case SDIR_NE:
1145 		sy -= TILE_HEIGHT;
1146 		ShiftGrid(&x, &y, 0, -1);
1147 		columns++;
1148 		rows += 2;
1149 		break;
1150 	case SDIR_E:
1151 		columns++;
1152 		break;
1153 	case SDIR_SE:
1154 		columns++;
1155 		rows++;
1156 		break;
1157 	case SDIR_S:
1158 		rows += 2;
1159 		break;
1160 	case SDIR_SW:
1161 		sx -= TILE_WIDTH;
1162 		ShiftGrid(&x, &y, -1, 0);
1163 		columns++;
1164 		rows++;
1165 		break;
1166 	case SDIR_W:
1167 		sx -= TILE_WIDTH;
1168 		ShiftGrid(&x, &y, -1, 0);
1169 		columns++;
1170 		break;
1171 	case SDIR_NW:
1172 		sx -= TILE_WIDTH / 2;
1173 		sy -= TILE_HEIGHT / 2;
1174 		x--;
1175 		columns++;
1176 		rows++;
1177 		break;
1178 	}
1179 
1180 	scrollrt_drawFloor(out, x, y, sx, sy, rows, columns);
1181 	scrollrt_draw(out, x, y, sx, sy, rows, columns);
1182 
1183 	if (!zoomflag) {
1184 		Zoom(full_out.subregionY(0, gnScreenHeight));
1185 	}
1186 }
1187 
1188 // DevilutionX extension.
1189 extern void DrawControllerModifierHints(CelOutputBuffer out);
1190 
DrawView(CelOutputBuffer out,int StartX,int StartY)1191 void DrawView(CelOutputBuffer out, int StartX, int StartY)
1192 {
1193 	DrawGame(out, StartX, StartY);
1194 	if (automapflag) {
1195 		DrawAutomap(out.subregionY(0, gnViewportHeight));
1196 	}
1197 	DrawMonsterHealthBar(out);
1198 
1199 	if (stextflag && !qtextflag)
1200 		DrawSText(out);
1201 	if (invflag) {
1202 		DrawInv(out);
1203 	} else if (sbookflag) {
1204 		DrawSpellBook(out);
1205 	}
1206 
1207 	DrawDurIcon(out);
1208 
1209 	if (chrflag) {
1210 		DrawChr(out);
1211 	} else if (questlog) {
1212 		DrawQuestLog(out);
1213 	}
1214 	if (!chrflag && plr[myplr]._pStatPts != 0 && !spselflag
1215 	    && (!questlog || gnScreenHeight >= SPANEL_HEIGHT + PANEL_HEIGHT + 74 || gnScreenWidth >= 4 * SPANEL_WIDTH)) {
1216 		DrawLevelUpIcon(out);
1217 	}
1218 	if (uitemflag) {
1219 		DrawUniqueInfo(out);
1220 	}
1221 	if (qtextflag) {
1222 		DrawQText(out);
1223 	}
1224 	if (spselflag) {
1225 		DrawSpellList(out);
1226 	}
1227 	if (dropGoldFlag) {
1228 		DrawGoldSplit(out, dropGoldValue);
1229 	}
1230 	if (helpflag) {
1231 		DrawHelp(out);
1232 	}
1233 	if (msgflag) {
1234 		DrawDiabloMsg(out);
1235 	}
1236 	if (deathflag) {
1237 		RedBack(out);
1238 	} else if (PauseMode != 0) {
1239 		gmenu_draw_pause(out);
1240 	}
1241 
1242 	DrawControllerModifierHints(out);
1243 	DrawPlrMsg(out);
1244 	gmenu_draw(out);
1245 	doom_draw(out);
1246 	DrawInfoBox(out);
1247 	DrawLifeFlask(out);
1248 	DrawManaFlask(out);
1249 }
1250 
1251 extern SDL_Surface *pal_surface;
1252 
1253 /**
1254  * @brief Render the whole screen black
1255  */
ClearScreenBuffer()1256 void ClearScreenBuffer()
1257 {
1258 	lock_buf(3);
1259 
1260 	assert(pal_surface != NULL);
1261 
1262 	SDL_Rect SrcRect = {
1263 		BUFFER_BORDER_LEFT,
1264 		BUFFER_BORDER_TOP,
1265 		gnScreenWidth,
1266 		gnScreenHeight,
1267 	};
1268 	SDL_FillRect(pal_surface, &SrcRect, 0);
1269 
1270 	unlock_buf(3);
1271 }
1272 
1273 #ifdef _DEBUG
1274 /**
1275  * @brief Scroll the screen when mouse is close to the edge
1276  */
ScrollView()1277 void ScrollView()
1278 {
1279 	BOOL scroll;
1280 
1281 	if (pcurs >= CURSOR_FIRSTITEM)
1282 		return;
1283 
1284 	scroll = FALSE;
1285 
1286 	if (MouseX < 20) {
1287 		if (dmaxy - 1 <= ViewY || dminx >= ViewX) {
1288 			if (dmaxy - 1 > ViewY) {
1289 				ViewY++;
1290 				scroll = TRUE;
1291 			}
1292 			if (dminx < ViewX) {
1293 				ViewX--;
1294 				scroll = TRUE;
1295 			}
1296 		} else {
1297 			ViewY++;
1298 			ViewX--;
1299 			scroll = TRUE;
1300 		}
1301 	}
1302 	if (MouseX > gnScreenWidth - 20) {
1303 		if (dmaxx - 1 <= ViewX || dminy >= ViewY) {
1304 			if (dmaxx - 1 > ViewX) {
1305 				ViewX++;
1306 				scroll = TRUE;
1307 			}
1308 			if (dminy < ViewY) {
1309 				ViewY--;
1310 				scroll = TRUE;
1311 			}
1312 		} else {
1313 			ViewY--;
1314 			ViewX++;
1315 			scroll = TRUE;
1316 		}
1317 	}
1318 	if (MouseY < 20) {
1319 		if (dminy >= ViewY || dminx >= ViewX) {
1320 			if (dminy < ViewY) {
1321 				ViewY--;
1322 				scroll = TRUE;
1323 			}
1324 			if (dminx < ViewX) {
1325 				ViewX--;
1326 				scroll = TRUE;
1327 			}
1328 		} else {
1329 			ViewX--;
1330 			ViewY--;
1331 			scroll = TRUE;
1332 		}
1333 	}
1334 	if (MouseY > gnScreenHeight - 20) {
1335 		if (dmaxy - 1 <= ViewY || dmaxx - 1 <= ViewX) {
1336 			if (dmaxy - 1 > ViewY) {
1337 				ViewY++;
1338 				scroll = TRUE;
1339 			}
1340 			if (dmaxx - 1 > ViewX) {
1341 				ViewX++;
1342 				scroll = TRUE;
1343 			}
1344 		} else {
1345 			ViewX++;
1346 			ViewY++;
1347 			scroll = TRUE;
1348 		}
1349 	}
1350 
1351 	if (scroll)
1352 		ScrollInfo._sdir = SDIR_NONE;
1353 }
1354 #endif
1355 
1356 /**
1357  * @brief Initialize the FPS meter
1358  */
EnableFrameCount()1359 void EnableFrameCount()
1360 {
1361 	frameflag = frameflag == 0;
1362 	framestart = SDL_GetTicks();
1363 }
1364 
1365 /**
1366  * @brief Display the current average FPS over 1 sec
1367  */
DrawFPS(CelOutputBuffer out)1368 static void DrawFPS(CelOutputBuffer out)
1369 {
1370 	DWORD tc, frames;
1371 	char String[12];
1372 
1373 	if (frameflag && gbActive && pPanelText) {
1374 		frameend++;
1375 		tc = SDL_GetTicks();
1376 		frames = tc - framestart;
1377 		if (tc - framestart >= 1000) {
1378 			framestart = tc;
1379 			framerate = 1000 * frameend / frames;
1380 			frameend = 0;
1381 		}
1382 		snprintf(String, 12, "%d FPS", framerate);
1383 		PrintGameStr(out, 8, 65, String, COL_RED);
1384 	}
1385 }
1386 
1387 /**
1388  * @brief Update part of the screen from the back buffer
1389  * @param dwX Back buffer coordinate
1390  * @param dwY Back buffer coordinate
1391  * @param dwWdt Back buffer coordinate
1392  * @param dwHgt Back buffer coordinate
1393  */
DoBlitScreen(Sint16 dwX,Sint16 dwY,Uint16 dwWdt,Uint16 dwHgt)1394 static void DoBlitScreen(Sint16 dwX, Sint16 dwY, Uint16 dwWdt, Uint16 dwHgt)
1395 {
1396 	// In SDL1 SDL_Rect x and y are Sint16. Cast explicitly to avoid a compiler warning.
1397 	using CoordType = decltype(SDL_Rect {}.x);
1398 	SDL_Rect src_rect {
1399 		static_cast<CoordType>(BUFFER_BORDER_LEFT + dwX),
1400 		static_cast<CoordType>(BUFFER_BORDER_TOP + dwY),
1401 		dwWdt, dwHgt
1402 	};
1403 	SDL_Rect dst_rect { dwX, dwY, dwWdt, dwHgt };
1404 
1405 	BltFast(&src_rect, &dst_rect);
1406 }
1407 
1408 /**
1409  * @brief Check render pipeline and blit individual screen parts
1410  * @param dwHgt Section of screen to update from top to bottom
1411  * @param draw_desc Render info box
1412  * @param draw_hp Render health bar
1413  * @param draw_mana Render mana bar
1414  * @param draw_sbar Render belt
1415  * @param draw_btn Render panel buttons
1416  */
DrawMain(int dwHgt,BOOL draw_desc,BOOL draw_hp,BOOL draw_mana,BOOL draw_sbar,BOOL draw_btn)1417 static void DrawMain(int dwHgt, BOOL draw_desc, BOOL draw_hp, BOOL draw_mana, BOOL draw_sbar, BOOL draw_btn)
1418 {
1419 	if (!gbActive) {
1420 		return;
1421 	}
1422 
1423 	assert(dwHgt >= 0 && dwHgt <= gnScreenHeight);
1424 
1425 	if (dwHgt > 0) {
1426 		DoBlitScreen(0, 0, gnScreenWidth, dwHgt);
1427 	}
1428 	if (dwHgt < gnScreenHeight) {
1429 		if (draw_sbar) {
1430 			DoBlitScreen(PANEL_LEFT + 204, PANEL_TOP + 5, 232, 28);
1431 		}
1432 		if (draw_desc) {
1433 			DoBlitScreen(PANEL_LEFT + 176, PANEL_TOP + 46, 288, 60);
1434 		}
1435 		if (draw_mana) {
1436 			DoBlitScreen(PANEL_LEFT + 460, PANEL_TOP, 88, 72);
1437 			DoBlitScreen(PANEL_LEFT + 564, PANEL_TOP + 64, 56, 56);
1438 		}
1439 		if (draw_hp) {
1440 			DoBlitScreen(PANEL_LEFT + 96, PANEL_TOP, 88, 72);
1441 		}
1442 		if (draw_btn) {
1443 			DoBlitScreen(PANEL_LEFT + 8, PANEL_TOP + 5, 72, 119);
1444 			DoBlitScreen(PANEL_LEFT + 556, PANEL_TOP + 5, 72, 48);
1445 			if (gbIsMultiplayer) {
1446 				DoBlitScreen(PANEL_LEFT + 84, PANEL_TOP + 91, 36, 32);
1447 				DoBlitScreen(PANEL_LEFT + 524, PANEL_TOP + 91, 36, 32);
1448 			}
1449 		}
1450 		if (sgdwCursWdtOld != 0) {
1451 			DoBlitScreen(sgdwCursXOld, sgdwCursYOld, sgdwCursWdtOld, sgdwCursHgtOld);
1452 		}
1453 		if (sgdwCursWdt != 0) {
1454 			DoBlitScreen(sgdwCursX, sgdwCursY, sgdwCursWdt, sgdwCursHgt);
1455 		}
1456 	}
1457 }
1458 
1459 /**
1460  * @brief Redraw screen
1461  * @param draw_cursor
1462  */
scrollrt_draw_game_screen(BOOL draw_cursor)1463 void scrollrt_draw_game_screen(BOOL draw_cursor)
1464 {
1465 	int hgt = 0;
1466 
1467 	if (force_redraw == 255) {
1468 		force_redraw = 0;
1469 		hgt = gnScreenHeight;
1470 	}
1471 
1472 	if (draw_cursor) {
1473 		lock_buf(0);
1474 		scrollrt_draw_cursor_item(GlobalBackBuffer());
1475 		unlock_buf(0);
1476 	}
1477 
1478 	DrawMain(hgt, FALSE, FALSE, FALSE, FALSE, FALSE);
1479 
1480 	if (draw_cursor) {
1481 		lock_buf(0);
1482 		scrollrt_draw_cursor_back_buffer(GlobalBackBuffer());
1483 		unlock_buf(0);
1484 	}
1485 	RenderPresent();
1486 }
1487 
1488 /**
1489  * @brief Render the game
1490  */
DrawAndBlit()1491 void DrawAndBlit()
1492 {
1493 	if (!gbRunGame) {
1494 		return;
1495 	}
1496 
1497 	int hgt = 0;
1498 	bool ddsdesc = false;
1499 	bool ctrlPan = false;
1500 
1501 	if (gnScreenWidth > PANEL_WIDTH || force_redraw == 255) {
1502 		drawhpflag = TRUE;
1503 		drawmanaflag = TRUE;
1504 		drawbtnflag = TRUE;
1505 		drawsbarflag = TRUE;
1506 		ddsdesc = false;
1507 		ctrlPan = true;
1508 		hgt = gnScreenHeight;
1509 	} else if (force_redraw == 1) {
1510 		ddsdesc = true;
1511 		ctrlPan = false;
1512 		hgt = gnViewportHeight;
1513 	}
1514 
1515 	force_redraw = 0;
1516 
1517 	lock_buf(0);
1518 	CelOutputBuffer out = GlobalBackBuffer();
1519 
1520 	DrawView(out, ViewX, ViewY);
1521 	if (ctrlPan) {
1522 		DrawCtrlPan(out);
1523 	}
1524 	if (drawhpflag) {
1525 		UpdateLifeFlask(out);
1526 	}
1527 	if (drawmanaflag) {
1528 		UpdateManaFlask(out);
1529 	}
1530 	if (drawbtnflag) {
1531 		DrawCtrlBtns(out);
1532 	}
1533 	if (drawsbarflag) {
1534 		DrawInvBelt(out);
1535 	}
1536 	if (talkflag) {
1537 		DrawTalkPan(out);
1538 		hgt = gnScreenHeight;
1539 	}
1540 	DrawXPBar(out);
1541 	scrollrt_draw_cursor_item(out);
1542 
1543 	DrawFPS(out);
1544 
1545 	unlock_buf(0);
1546 
1547 	DrawMain(hgt, ddsdesc, drawhpflag, drawmanaflag, drawsbarflag, drawbtnflag);
1548 
1549 	lock_buf(0);
1550 	scrollrt_draw_cursor_back_buffer(GlobalBackBuffer());
1551 	unlock_buf(0);
1552 	RenderPresent();
1553 
1554 	drawhpflag = FALSE;
1555 	drawmanaflag = FALSE;
1556 	drawbtnflag = FALSE;
1557 	drawsbarflag = FALSE;
1558 }
1559 
1560 DEVILUTION_END_NAMESPACE
1561