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