1 /** @file hu_stuff.cpp Miscellaneous routines for heads-up displays and UI.
2 *
3 * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 * @authors Copyright © 2005-2015 Daniel Swanson <danij@dengine.net>
5 * @authors Copyright © 1993-1996 by id Software, Inc.
6 *
7 * @par License
8 * GPL: http://www.gnu.org/licenses/gpl.html
9 *
10 * <small>This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by the
12 * Free Software Foundation; either version 2 of the License, or (at your
13 * option) any later version. This program is distributed in the hope that it
14 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
15 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
16 * Public License for more details. You should have received a copy of the GNU
17 * General Public License along with this program; if not, write to the Free
18 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19 * 02110-1301 USA</small>
20 */
21
22 #include "common.h"
23 #include "hu_stuff.h"
24
25 #include <cstdlib>
26 #include <cctype>
27 #include <cmath>
28 #include <cstdio>
29 #include <cstring>
30 #include <map>
31 #include <de/String>
32 #include "fi_lib.h"
33 #include "g_common.h"
34 #include "g_defs.h"
35 #include "gamesession.h"
36 #include "hu_inventory.h"
37 #include "hu_menu.h"
38 #include "hu_msg.h"
39 #include "hud/automapstyle.h"
40 #include "p_mapsetup.h"
41 #include "p_tick.h"
42 #include "r_common.h"
43
44 using namespace de;
45 using namespace common;
46
47 /**
48 * @defgroup tableColumnFlags Table Column flags
49 */
50 ///@{
51 #define CF_HIDE 0x0001 ///< Column is presently hidden (will be collapsed).
52 #define CF_STRETCH_WIDTH 0x0002 ///< Column width is not fixed.
53 ///@}
54
55 /**
56 * Defines a column in a table layout.
57 */
58 typedef struct {
59 const char* label;
60 int type;
61 int flags; ///< @ref tableColumnFlags
62 int alignFlags; ///< @ref alignFlags
63
64 /// Auto-initialized:
65 float x;
66 float width;
67 } column_t;
68
69 /**
70 * Player "score" information, kills, suicides, etc...
71 */
72 typedef struct {
73 int player, pClass, team;
74 int kills, suicides;
75 float color[3];
76 } scoreinfo_t;
77
78 /**
79 * Per-player scoreboard state.
80 */
81 typedef struct {
82 int hideTics;
83 float alpha;
84 } scoreboardstate_t;
85
86 typedef struct fogeffectlayer_s {
87 float texOffset[2];
88 float texAngle;
89 float posAngle;
90 } fogeffectlayer_t;
91
92 typedef struct fogeffectdata_s {
93 DGLuint texture;
94 float alpha, targetAlpha;
95 fogeffectlayer_t layers[2];
96 float joinY;
97 dd_bool scrollDir;
98 } fogeffectdata_t;
99
100 fontid_t fonts[NUM_GAME_FONTS];
101
102 #if __JDOOM__ || __JDOOM64__
103 /// The end message strings.
104 const char* endmsg[NUM_QUITMESSAGES + 1];
105 #endif
106
107 dd_bool shiftdown = false;
108 const char shiftXForm[] = {
109 0,
110 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
111 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
112 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
113 31,
114 ' ', '!', '"', '#', '$', '%', '&',
115 '"', // shift-'
116 '(', ')', '*', '+',
117 '<', // shift-,
118 '_', // shift--
119 '>', // shift-.
120 '?', // shift-/
121 ')', // shift-0
122 '!', // shift-1
123 '@', // shift-2
124 '#', // shift-3
125 '$', // shift-4
126 '%', // shift-5
127 '^', // shift-6
128 '&', // shift-7
129 '*', // shift-8
130 '(', // shift-9
131 ':',
132 ':', // shift-;
133 '<',
134 '+', // shift-=
135 '>', '?', '@',
136 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
137 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
138 '[', // shift-[
139 '!', // shift-backslash
140 ']', // shift-]
141 '"', '_',
142 '\'', // shift-`
143 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
144 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
145 '{', '|', '}', '~', 127
146 };
147
148 // Misc UI patches:
149 patchid_t borderPatches[8];
150 #if __JHERETIC__ || __JHEXEN__
151 patchid_t pInvItemBox;
152 patchid_t pInvSelectBox;
153 patchid_t pInvPageLeft[2];
154 patchid_t pInvPageRight[2];
155 #endif
156 static patchid_t m_pause; // Paused graphic.
157
158 // Menu and message background fog effect state.
159 static fogeffectdata_t fogEffectData;
160
161 // Scoreboard states for each player.
162 static scoreboardstate_t scoreStates[MAXPLAYERS];
163
164 // Patch => Text Replacement LUT.
165 typedef std::map<patchid_t, int> PatchReplacementValues;
166 PatchReplacementValues patchReplacements;
167
patchReplacementValueIndex(patchid_t patchId,bool canCreate=true)168 static int patchReplacementValueIndex(patchid_t patchId, bool canCreate = true)
169 {
170 // Have we previously encountered this?
171 PatchReplacementValues::const_iterator found = patchReplacements.find(patchId);
172 if(found != patchReplacements.end()) return found->second;
173
174 // No. Look it up.
175 dint valueIndex = -1;
176 auto const patchPath = String(Str_Text(R_ComposePatchPath(patchId)));
177 if(!patchPath.isEmpty())
178 {
179 valueIndex = Defs().getValueNum("Patch Replacement|" + patchPath);
180 }
181
182 if(canCreate)
183 {
184 patchReplacements.insert(std::pair<patchid_t, dint>(patchId, valueIndex));
185 }
186
187 return valueIndex;
188 }
189
190 /**
191 * Intialize the background fog effect.
192 */
initFogEffect()193 static void initFogEffect()
194 {
195 fogeffectdata_t *fog = &fogEffectData;
196
197 fog->texture = 0;
198 fog->alpha = fog->targetAlpha = 0;
199 fog->joinY = 0.5f;
200 fog->scrollDir = true;
201
202 fog->layers[0].texOffset[VX] = fog->layers[0].texOffset[VY] = 0;
203 fog->layers[0].texAngle = 93;
204 fog->layers[0].posAngle = 35;
205
206 fog->layers[1].texOffset[VX] = fog->layers[1].texOffset[VY] = 0;
207 fog->layers[1].texAngle = 12;
208 fog->layers[1].posAngle = 77;
209 }
210
prepareFogTexture()211 static void prepareFogTexture()
212 {
213 if(Get(DD_NOVIDEO)) return;
214 // Already prepared?
215 if(fogEffectData.texture) return;
216
217 if(CentralLumpIndex().contains("menufog.lmp"))
218 {
219 de::File1 &lump = CentralLumpIndex()[CentralLumpIndex().findLast("menufog.lmp")];
220 uint8_t const *pixels = lump.cache();
221 /// @todo fixme: Do not assume dimensions.
222 fogEffectData.texture = DGL_NewTextureWithParams(DGL_LUMINANCE, 64, 64,
223 pixels, 0, DGL_NEAREST, DGL_LINEAR, -1 /*best anisotropy*/, DGL_REPEAT, DGL_REPEAT);
224
225 lump.unlock();
226 }
227 }
228
releaseFogTexture()229 static void releaseFogTexture()
230 {
231 if(Get(DD_NOVIDEO)) return;
232 // Not prepared?
233 if(!fogEffectData.texture) return;
234
235 DGL_DeleteTextures(1, (DGLuint *) &fogEffectData.texture);
236 fogEffectData.texture = 0;
237 }
238
declareGraphicPatches()239 static void declareGraphicPatches()
240 {
241 // View border patches:
242 for(uint i = 1; i < 9; ++i)
243 {
244 borderPatches[i-1] = R_DeclarePatch(borderGraphics[i]);
245 }
246
247 #if __JDOOM__ || __JDOOM64__
248 m_pause = R_DeclarePatch("M_PAUSE");
249 #elif __JHERETIC__ || __JHEXEN__
250 m_pause = R_DeclarePatch("PAUSED");
251 #endif
252
253 #if __JHERETIC__ || __JHEXEN__
254 pInvItemBox = R_DeclarePatch("ARTIBOX");
255 pInvSelectBox = R_DeclarePatch("SELECTBO");
256 pInvPageLeft[0] = R_DeclarePatch("INVGEML1");
257 pInvPageLeft[1] = R_DeclarePatch("INVGEML2");
258 pInvPageRight[0] = R_DeclarePatch("INVGEMR1");
259 pInvPageRight[1] = R_DeclarePatch("INVGEMR2");
260 #endif
261 }
262
263 /**
264 * Loads the font patches and inits various strings
265 * JHEXEN Note: Don't bother with the yellow font, we'll colour the white version
266 */
Hu_LoadData()267 void Hu_LoadData()
268 {
269 // Clear the patch replacement value map (definitions have been re-read).
270 patchReplacements.clear();
271
272 initFogEffect();
273 prepareFogTexture();
274
275 declareGraphicPatches();
276
277 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__
278 R_GetGammaMessageStrings();
279 #endif
280
281 #if __JDOOM__ || __JDOOM64__
282 // Quit messages:
283 endmsg[0] = GET_TXT(TXT_QUITMSG);
284 for(uint i = 1; i <= NUM_QUITMESSAGES; ++i)
285 {
286 endmsg[i] = GET_TXT(TXT_QUITMESSAGE1 + i - 1);
287 }
288 #endif
289 }
290
Hu_UnloadData()291 void Hu_UnloadData()
292 {
293 releaseFogTexture();
294 }
295
296 #if __JHERETIC__ || __JHEXEN__
drawQuad(float x,float y,float w,float h,float s,float t,float r,float g,float b,float a)297 static void drawQuad(float x, float y, float w, float h, float s, float t,
298 float r, float g, float b, float a)
299 {
300 DGL_Color4f(r, g, b, a);
301 DGL_Begin(DGL_QUADS);
302 DGL_TexCoord2f(0, 0 * s, 0);
303 DGL_Vertex2f(x, y);
304
305 DGL_TexCoord2f(0, 1 * s, 0);
306 DGL_Vertex2f(x + w, y);
307
308 DGL_TexCoord2f(0, 1 * s, t);
309 DGL_Vertex2f(x + w, y + h);
310
311 DGL_TexCoord2f(0, 0 * s, t);
312 DGL_Vertex2f(x, y + h);
313 DGL_End();
314 }
315 #endif
316
HU_DrawText(const char * str,float x,float y,float scale,float r,float g,float b,float a,int alignFlags,short textFlags)317 void HU_DrawText(const char* str, float x, float y, float scale,
318 float r, float g, float b, float a, int alignFlags, short textFlags)
319 {
320 if(!str || !str[0]) return;
321
322 const bool applyScale = !FEQUAL(scale, 1.0f);
323 if(applyScale)
324 {
325 DGL_MatrixMode(DGL_MODELVIEW);
326 DGL_PushMatrix();
327
328 DGL_Translatef(x, y, 0);
329 DGL_Scalef(scale, scale, 1);
330 DGL_Translatef(-x, -y, 0);
331 }
332
333 FR_SetColorAndAlpha(r, g, b, a);
334 FR_DrawTextXY3(str, x, y, alignFlags, textFlags);
335
336 if(applyScale)
337 {
338 DGL_MatrixMode(DGL_MODELVIEW);
339 DGL_PopMatrix();
340 }
341 }
342
343 /// Predicate for sorting score infos.
scoreInfoCompare(void const * a_,void const * b_)344 int scoreInfoCompare(void const *a_, void const *b_)
345 {
346 scoreinfo_t const *a = (scoreinfo_t *) a_;
347 scoreinfo_t const *b = (scoreinfo_t *) b_;
348
349 if(a->kills > b->kills) return -1;
350 if(b->kills > a->kills) return 1;
351
352 if(gfw_Rule(deathmatch))
353 {
354 // In deathmatch, suicides affect your place on the scoreboard.
355 if(a->suicides < b->suicides) return -1;
356 if(b->suicides < a->suicides) return 1;
357 }
358
359 return 0;
360 }
361
sortScoreInfo(scoreinfo_t * vec,size_t size)362 static void sortScoreInfo(scoreinfo_t* vec, size_t size)
363 {
364 qsort(vec, size, sizeof(scoreinfo_t), scoreInfoCompare);
365 }
366
populateScoreInfo(scoreinfo_t * scoreBoard,int maxPlayers,int player)367 static int populateScoreInfo(scoreinfo_t* scoreBoard, int maxPlayers, int player)
368 {
369 DENG_UNUSED(player);
370
371 #if __JHEXEN__
372 static const int plrColors[] = {
373 AM_PLR1_COLOR,
374 AM_PLR2_COLOR,
375 AM_PLR3_COLOR,
376 AM_PLR4_COLOR,
377 AM_PLR5_COLOR,
378 AM_PLR6_COLOR,
379 AM_PLR7_COLOR,
380 AM_PLR8_COLOR
381 };
382 #else
383 static const float green[3] = { 0.f, .8f, 0.f };
384 static const float gray[3] = { .45f, .45f, .45f };
385 static const float brown[3] = { .7f, .5f, .4f };
386 static const float red[3] = { 1.f, 0.f, 0.f };
387 #endif
388 int n = 0, inCount;
389
390 memset(scoreBoard, 0, sizeof(*scoreBoard) * maxPlayers);
391 inCount = 0;
392 for(int i = 0; i < maxPlayers; ++i)
393 {
394 player_t* plr = &players[i];
395 scoreinfo_t* info;
396
397 if(!plr->plr->inGame) continue;
398
399 inCount++;
400 info = &scoreBoard[n++];
401 info->player = i;
402 #if __JHERETIC__
403 info->pClass = (plr->morphTics > 0? PCLASS_CHICKEN : PCLASS_PLAYER);
404 #elif __JHEXEN__
405 if(plr->morphTics > 0)
406 info->pClass = PCLASS_PIG;
407 else
408 info->pClass = plr->class_;
409 #else
410 info->pClass = PCLASS_PLAYER;
411 #endif
412 info->team = cfg.playerColor[i];
413
414 // Pick team color:
415 #if __JHEXEN__
416 R_GetColorPaletteRGBf(0, plrColors[info->team], info->color, false);
417 #else
418 switch(info->team)
419 {
420 case 0: memcpy(info->color, green, sizeof(float)*3); break;
421 case 1: memcpy(info->color, gray, sizeof(float)*3); break;
422 case 2: memcpy(info->color, brown, sizeof(float)*3); break;
423 case 3: memcpy(info->color, red, sizeof(float)*3); break;
424 }
425 #endif
426
427 if(gfw_Rule(deathmatch))
428 {
429 for(int j = 0; j < maxPlayers; ++j)
430 {
431 if(j != i)
432 {
433 info->kills += plr->frags[j];
434 }
435 else
436 {
437 #if __JHEXEN__
438 info->suicides += -plr->frags[j];
439 #else
440 info->suicides += plr->frags[j];
441 #endif
442 }
443 }
444 }
445 else
446 {
447 info->kills = plr->killCount;
448 info->suicides = 0; // We don't care anyway.
449 }
450 }
451
452 sortScoreInfo(scoreBoard, n);
453
454 return inCount;
455 }
456
HU_ScoreBoardUnHide(int player)457 void HU_ScoreBoardUnHide(int player)
458 {
459 if(player < 0 || player >= MAXPLAYERS) return;
460
461 player_t* plr = &players[player];
462 if(!plr->plr->inGame) return;
463
464 scoreboardstate_t* ss = &scoreStates[player];
465 ss->alpha = 1;
466 ss->hideTics = 35;
467 }
468
countTableColumns(const column_t * columns)469 static int countTableColumns(const column_t* columns)
470 {
471 int count = 0;
472 while(columns[count].label){ count++; }
473 return count;
474 }
475
applyTableLayout(column_t * columns,int numCols,float x,float width,int cellPadding=0)476 static void applyTableLayout(column_t* columns, int numCols, float x, float width,
477 int cellPadding = 0)
478 {
479 DENG_ASSERT(columns);
480 column_t* col;
481
482 col = columns;
483 int numStretchCols = 0;
484 for(int n = 0; n < numCols; ++n, col++)
485 {
486 col->x = 0;
487 col->width = 0;
488
489 // Count the total number of non-fixed width ("stretched") columns.
490 if(!(col->flags & CF_HIDE) && (col->flags & CF_STRETCH_WIDTH))
491 numStretchCols++;
492 }
493
494 col = columns;
495 int fixedWidth = 0;
496 for(int n = 0; n < numCols; ++n, col++)
497 {
498 col->width = 0;
499 if(col->flags & CF_HIDE) continue; // Collapse.
500
501 if(!(col->flags & CF_STRETCH_WIDTH))
502 {
503 col->width = FR_TextWidth(col->label) + cellPadding * 2;
504 fixedWidth += col->width;
505 }
506 }
507
508 col = columns;
509 for(int n = 0; n < numCols; ++n, col++)
510 {
511 if(col->flags & CF_HIDE) continue; // Collapse.
512
513 if(col->flags & CF_STRETCH_WIDTH)
514 {
515 col->width = (width - fixedWidth) / numStretchCols;
516 }
517 }
518
519 col = columns;
520 for(int n = 0; n < numCols; ++n, col++)
521 {
522 col->x = x;
523 if(!(col->flags & CF_HIDE)) // Collapse.
524 {
525 x += col->width;
526 }
527 }
528 }
529
drawTable(float x,float ly,float width,float height,column_t * columns,scoreinfo_t * scoreBoard,int inCount,float alpha,int player)530 static void drawTable(float x, float ly, float width, float height,
531 column_t* columns, scoreinfo_t* scoreBoard, int inCount, float alpha,
532 int player)
533 {
534 #define CELL_PADDING 1
535
536 if(!columns) return;
537 if(!(alpha > 0)) return;
538
539 int numCols = countTableColumns(columns);
540 if(!numCols) return;
541
542 const int lineHeight = .5f + height / (MAXPLAYERS + 1);
543 const int fontHeight = FR_CharHeight('A');
544 float fontScale = float(lineHeight) / (fontHeight + CELL_PADDING * 2);
545
546 #ifdef __JHEXEN__
547 // Quick fix for Hexen's oversized scoreboard fonts.
548 // Something is broken here, though: the above should calculate a
549 // suitable font size for all games? -jk
550 fontScale *= .55f;
551 #endif
552
553 applyTableLayout(columns, numCols, x, width, CELL_PADDING);
554
555 /// @todo fixme: all layout/placement should be done in applyTableLayout()
556
557 // Draw the table header:
558 DGL_Enable(DGL_TEXTURE_2D);
559 const column_t* col = columns;
560 for(int n = 0; n < numCols; ++n, col++)
561 {
562 if(col->flags & CF_HIDE) continue;
563
564 int cX = col->x;
565 if(col->alignFlags & ALIGN_RIGHT) cX += col->width - CELL_PADDING;
566 else cX += CELL_PADDING;
567
568 int cY = ly + lineHeight - CELL_PADDING;
569 int alignFlags = (col->alignFlags & ~ALIGN_TOP) | ALIGN_BOTTOM;
570
571 HU_DrawText(col->label, cX, cY, fontScale, 1.f, 1.f, 1.f, alpha, alignFlags, DTF_ONLY_SHADOW);
572 }
573 ly += lineHeight;
574 DGL_Disable(DGL_TEXTURE_2D);
575
576 // Draw the table from left to right, top to bottom:
577 for(int i = 0; i < inCount; ++i, ly += lineHeight)
578 {
579 scoreinfo_t* info = &scoreBoard[i];
580 const char* name = Net_GetPlayerName(info->player);
581 char buf[5];
582
583 if(info->player == player)
584 {
585 // Draw a background to make *me* stand out.
586 float val = (info->color[0] + info->color[1] + info->color[2]) / 3;
587
588 if(val < .5f) val = .2f;
589 else val = .8f;
590
591 DGL_DrawRectf2Color(x, ly, width, lineHeight, val + .2f, val + .2f, val, .5f * alpha);
592 }
593
594 // Now draw the fields:
595 DGL_Enable(DGL_TEXTURE_2D);
596 const column_t* col = columns;
597 for(int n = 0; n < numCols; ++n, col++)
598 {
599 if(col->flags & CF_HIDE) continue;
600
601 int cX = col->x;
602 if(col->alignFlags & ALIGN_RIGHT) cX += col->width - CELL_PADDING;
603 else cX += CELL_PADDING;
604
605 int cY = ly + CELL_PADDING;
606
607 switch(col->type)
608 {
609 case 0: // Class icon.
610 {
611 #if __JHERETIC__ || __JHEXEN__
612 int spr = 0;
613 # if __JHERETIC__
614 if(info->pClass == PCLASS_CHICKEN)
615 spr = SPR_CHKN;
616 # else
617 switch(info->pClass)
618 {
619 case PCLASS_FIGHTER: spr = SPR_PLAY; break;
620 case PCLASS_CLERIC: spr = SPR_CLER; break;
621 case PCLASS_MAGE: spr = SPR_MAGE; break;
622 case PCLASS_PIG: spr = SPR_PIGY; break;
623 }
624 # endif
625 if(spr)
626 {
627 spriteinfo_t sprInfo;
628 int w, h;
629 float scale;
630
631 R_GetSpriteInfo(spr, 0, &sprInfo);
632 w = sprInfo.geometry.size.width;
633 h = sprInfo.geometry.size.height;
634
635 if(h > w) scale = float(lineHeight - CELL_PADDING * 2) / h;
636 else scale = float(col->width - CELL_PADDING * 2) / w;
637
638 w *= scale;
639 h *= scale;
640
641 // Align to center on both X+Y axes.
642 cX += ((col->width - CELL_PADDING * 2) - w) / 2.0f;
643 cY += ((lineHeight - CELL_PADDING * 2) - h) / 2.0f;
644
645 DGL_SetMaterialUI(sprInfo.material, DGL_CLAMP_TO_EDGE, DGL_CLAMP_TO_EDGE);
646
647 drawQuad(cX, cY, w, h, sprInfo.texCoord[0], sprInfo.texCoord[1], 1, 1, 1, alpha);
648 }
649 #endif
650 break;
651 }
652
653 case 1: // Name.
654 HU_DrawText(name, cX, cY, fontScale,
655 info->color[0], info->color[1], info->color[2],
656 alpha, col->alignFlags, DTF_ONLY_SHADOW);
657 break;
658
659 case 2: // #Suicides.
660 sprintf(buf, "%4i", info->suicides);
661 HU_DrawText(buf, cX, cY, fontScale,
662 info->color[0], info->color[1], info->color[2],
663 alpha, col->alignFlags, DTF_ONLY_SHADOW);
664 break;
665
666 case 3: // #Kills.
667 sprintf(buf, "%4i", info->kills);
668 HU_DrawText(buf, cX, cY, fontScale,
669 info->color[0], info->color[1], info->color[2],
670 alpha, col->alignFlags, DTF_ONLY_SHADOW);
671 break;
672 }
673 }
674
675 DGL_Disable(DGL_TEXTURE_2D);
676 }
677
678 #undef CELL_PADDING
679 }
680
drawMapMetaData(float x,float y,float alpha)681 static void drawMapMetaData(float x, float y, float alpha)
682 {
683 #define BORDER 2
684
685 de::String title = G_MapTitle(gfw_Session()->mapUri());
686 if(title.isEmpty()) title = "Unnamed";
687
688 char buf[256];
689 dd_snprintf(buf, 256, "%s - %s", gfw_Session()->rules().description().toLatin1().constData(),
690 title.toLatin1().constData());
691
692 FR_SetColorAndAlpha(1, 1, 1, alpha);
693 FR_DrawTextXY2(buf, x + BORDER, y - BORDER, ALIGN_BOTTOMLEFT);
694
695 #undef BORDER
696 }
697
698 /**
699 * Draws a sorted frags list in the lower right corner of the screen.
700 */
HU_DrawScoreBoard(int player)701 void HU_DrawScoreBoard(int player)
702 {
703 #define LINE_BORDER 4
704
705 static column_t columns[] = {
706 { "cl", 0, 0, 0, 0, 0 },
707 { "name", 1, CF_STRETCH_WIDTH, ALIGN_TOPLEFT, 0, 0 },
708 { "suicides", 2, 0, ALIGN_TOPRIGHT, 0, 0 },
709 { "frags", 3, 0, ALIGN_TOPRIGHT, 0, 0 },
710 { NULL, 0, 0, 0, 0, 0 }
711 };
712 static bool tableInitialized = false;
713
714 if(!IS_NETGAME) return;
715
716 if(player < 0 || player >= MAXPLAYERS) return;
717 scoreboardstate_t* ss = &scoreStates[player];
718
719 if(!(ss->alpha > 0)) return;
720
721 // Are we yet to initialize the table for this game mode?
722 if(!tableInitialized)
723 {
724 // Only display the player class column if more than one class is defined.
725 if(NUM_PLAYER_CLASSES == 1)
726 columns[0].flags |= CF_HIDE;
727 tableInitialized = true;
728 }
729
730 // Set up the fixed 320x200 projection.
731 DGL_MatrixMode(DGL_PROJECTION);
732 DGL_PushMatrix();
733 DGL_LoadIdentity();
734 DGL_Ortho(0, 0, SCREENWIDTH, SCREENHEIGHT, -1, 1);
735
736 // Populate and sort the scoreboard according to game rules, type, etc.
737 scoreinfo_t scoreBoard[MAXPLAYERS];
738 int inCount = populateScoreInfo(scoreBoard, MAXPLAYERS, player);
739
740 // Determine the dimensions of the scoreboard:
741 RectRaw geom = {{0, 0}, {SCREENWIDTH - 32, SCREENHEIGHT - 32}};
742
743 DGL_MatrixMode(DGL_MODELVIEW);
744 DGL_PushMatrix();
745 DGL_Translatef(16, 16, 0);
746
747 // Draw a background around the whole thing.
748 DGL_DrawRectf2Color(geom.origin.x, geom.origin.y, geom.size.width, geom.size.height,
749 0, 0, 0, .4f * ss->alpha);
750
751 DGL_Enable(DGL_TEXTURE_2D);
752
753 FR_SetFont(FID(GF_FONTB));
754 FR_LoadDefaultAttrib();
755 FR_SetLeading(0);
756 FR_SetColorAndAlpha(1, 0, 0, ss->alpha);
757 FR_DrawTextXY3("ranking", geom.origin.x + geom.size.width / 2, geom.origin.y + LINE_BORDER, ALIGN_TOP, DTF_ONLY_SHADOW);
758
759 FR_SetFont(FID(GF_FONTA));
760 drawMapMetaData(geom.origin.x, geom.origin.y + geom.size.height, ss->alpha);
761 drawTable(geom.origin.x, geom.origin.y + 20, geom.size.width, geom.size.height - 20,
762 columns, scoreBoard, inCount, ss->alpha, player);
763
764 DGL_Disable(DGL_TEXTURE_2D);
765
766 // Restore earlier matrices.
767 DGL_MatrixMode(DGL_PROJECTION);
768 DGL_PopMatrix();
769 DGL_MatrixMode(DGL_MODELVIEW);
770 DGL_PopMatrix();
771 }
772
Hu_Ticker(void)773 void Hu_Ticker(void)
774 {
775 int i;
776 for(i = 0; i < MAXPLAYERS; ++i)
777 {
778 scoreboardstate_t* ss = &scoreStates[i];
779 player_t* plr = &players[i];
780
781 if(!plr->plr->inGame) continue;
782
783 if(ss->hideTics > 0)
784 {
785 --ss->hideTics;
786 }
787 else
788 {
789 if(ss->alpha > 0)
790 ss->alpha -= .05f;
791 }
792 }
793 }
794
795 /**
796 * Updates on Game Tick.
797 */
Hu_FogEffectTicker(timespan_t ticLength)798 void Hu_FogEffectTicker(timespan_t ticLength)
799 {
800 #define fog (&fogEffectData)
801 #define FOGALPHA_FADE_STEP (.07f)
802
803 static const float MENUFOGSPEED[2] = {.03f, -.085f};
804 int i;
805
806 if(cfg.common.hudFog == 0)
807 return;
808
809 // Move towards the target alpha
810 if(fog->alpha != fog->targetAlpha)
811 {
812 float diff = fog->targetAlpha - fog->alpha;
813
814 if(fabs(diff) > FOGALPHA_FADE_STEP)
815 {
816 fog->alpha += FOGALPHA_FADE_STEP * ticLength * TICRATE * (diff > 0? 1 : -1);
817 }
818 else
819 {
820 fog->alpha = fog->targetAlpha;
821 }
822 }
823
824 if(!(fog->alpha > 0))
825 return;
826
827 for(i = 0; i < 2; ++i)
828 {
829 if(cfg.common.hudFog == 2)
830 {
831 fog->layers[i].texAngle += ((MENUFOGSPEED[i]/4) * ticLength * TICRATE);
832 fog->layers[i].posAngle -= (MENUFOGSPEED[!i] * ticLength * TICRATE);
833 fog->layers[i].texOffset[VX] = 160 + 120 * cos(fog->layers[i].posAngle / 180 * DD_PI);
834 fog->layers[i].texOffset[VY] = 100 + 100 * sin(fog->layers[i].posAngle / 180 * DD_PI);
835 }
836 else
837 {
838 fog->layers[i].texAngle += ((MENUFOGSPEED[i]/4) * ticLength * TICRATE);
839 fog->layers[i].posAngle -= ((MENUFOGSPEED[!i]*1.5f) * ticLength * TICRATE);
840 fog->layers[i].texOffset[VX] = 320 + 320 * cos(fog->layers[i].posAngle / 180 * DD_PI);
841 fog->layers[i].texOffset[VY] = 240 + 240 * sin(fog->layers[i].posAngle / 180 * DD_PI);
842 }
843 }
844
845 // Calculate the height of the menuFog 3 Y join
846 if(cfg.common.hudFog == 4)
847 {
848 if(fog->scrollDir && fog->joinY > 0.46f)
849 fog->joinY = fog->joinY / 1.002f;
850 else if(!fog->scrollDir && fog->joinY < 0.54f )
851 fog->joinY = fog->joinY * 1.002f;
852
853 if((fog->joinY < 0.46f || fog->joinY > 0.54f))
854 fog->scrollDir = !fog->scrollDir;
855 }
856 #undef fog
857 #undef FOGALPHA_FADE_STEP
858 }
859
M_DrawGlowBar(const float a[2],const float b[2],float thickness,dd_bool left,dd_bool right,dd_bool caps,float red,float green,float blue,float alpha)860 void M_DrawGlowBar(const float a[2], const float b[2], float thickness,
861 dd_bool left, dd_bool right, dd_bool caps, float red, float green,
862 float blue, float alpha)
863 {
864 float length, delta[2], normal[2], unit[2];
865 DGLuint tex;
866
867 if(!left && !right && !caps)
868 return;
869 if(!(alpha > 0))
870 return;
871
872 delta[0] = b[0] - a[0];
873 delta[1] = b[1] - a[1];
874 length = sqrt(delta[0] * delta[0] + delta[1] * delta[1]);
875 if(length <= 0)
876 return;
877
878 unit[0] = delta[0] / length;
879 unit[1] = delta[1] / length;
880 normal[0] = unit[1];
881 normal[1] = -unit[0];
882
883 tex = Get(DD_DYNLIGHT_TEXTURE);
884
885 if(caps)
886 { // Draw a "cap" at the start of the line.
887 float v1[2], v2[2], v3[2], v4[2];
888
889 v1[0] = a[0] - unit[0] * thickness + normal[0] * thickness;
890 v1[1] = a[1] - unit[1] * thickness + normal[1] * thickness;
891 v2[0] = a[0] + normal[0] * thickness;
892 v2[1] = a[1] + normal[1] * thickness;
893 v3[0] = a[0] - normal[0] * thickness;
894 v3[1] = a[1] - normal[1] * thickness;
895 v4[0] = a[0] - unit[0] * thickness - normal[0] * thickness;
896 v4[1] = a[1] - unit[1] * thickness - normal[1] * thickness;
897
898 DGL_Bind(tex);
899 DGL_Color4f(red, green, blue, alpha);
900
901 DGL_Begin(DGL_QUADS);
902 DGL_TexCoord2f(0, 0, 0);
903 DGL_Vertex2f(v1[0], v1[1]);
904
905 DGL_TexCoord2f(0, .5f, 0);
906 DGL_Vertex2f(v2[0], v2[1]);
907
908 DGL_TexCoord2f(0, .5f, 1);
909 DGL_Vertex2f(v3[0], v3[1]);
910
911 DGL_TexCoord2f(0, 0, 1);
912 DGL_Vertex2f(v4[0], v4[1]);
913 DGL_End();
914 }
915
916 // The middle part of the line.
917 if(left && right)
918 {
919 float v1[2], v2[2], v3[2], v4[2];
920
921 v1[0] = a[0] + normal[0] * thickness;
922 v1[1] = a[1] + normal[1] * thickness;
923 v2[0] = b[0] + normal[0] * thickness;
924 v2[1] = b[1] + normal[1] * thickness;
925 v3[0] = b[0] - normal[0] * thickness;
926 v3[1] = b[1] - normal[1] * thickness;
927 v4[0] = a[0] - normal[0] * thickness;
928 v4[1] = a[1] - normal[1] * thickness;
929
930 DGL_Bind(tex);
931 DGL_Color4f(red, green, blue, alpha);
932
933 DGL_Begin(DGL_QUADS);
934 DGL_TexCoord2f(0, .5f, 0);
935 DGL_Vertex2f(v1[0], v1[1]);
936
937 DGL_TexCoord2f(0, .5f, 0);
938 DGL_Vertex2f(v2[0], v2[1]);
939
940 DGL_TexCoord2f(0, .5f, 1);
941 DGL_Vertex2f(v3[0], v3[1]);
942
943 DGL_TexCoord2f(0, .5f, 1);
944 DGL_Vertex2f(v4[0], v4[1]);
945 DGL_End();
946 }
947 else if(left)
948 {
949 float v1[2], v2[2], v3[2], v4[2];
950
951 v1[0] = a[0] + normal[0] * thickness;
952 v1[1] = a[1] + normal[1] * thickness;
953 v2[0] = b[0] + normal[0] * thickness;
954 v2[1] = b[1] + normal[1] * thickness;
955 v3[0] = b[0];
956 v3[1] = b[1];
957 v4[0] = a[0];
958 v4[1] = a[1];
959
960 DGL_Bind(tex);
961 DGL_Color4f(red, green, blue, alpha);
962
963 DGL_Begin(DGL_QUADS);
964 DGL_TexCoord2f(0, 0, .25f);
965 DGL_Vertex2f(v1[0], v1[1]);
966
967 DGL_TexCoord2f(0, 0, .25f);
968 DGL_Vertex2f(v2[0], v2[1]);
969
970 DGL_TexCoord2f(0, .5f, .25f);
971 DGL_Vertex2f(v3[0], v3[1]);
972
973 DGL_TexCoord2f(0, .5f, .25f);
974 DGL_Vertex2f(v4[0], v4[1]);
975 DGL_End();
976 }
977 else // right
978 {
979 float v1[2], v2[2], v3[2], v4[2];
980
981 v1[0] = a[0];
982 v1[1] = a[1];
983 v2[0] = b[0];
984 v2[1] = b[1];
985 v3[0] = b[0] - normal[0] * thickness;
986 v3[1] = b[1] - normal[1] * thickness;
987 v4[0] = a[0] - normal[0] * thickness;
988 v4[1] = a[1] - normal[1] * thickness;
989
990 DGL_Bind(tex);
991 DGL_Color4f(red, green, blue, alpha);
992
993 DGL_Begin(DGL_QUADS);
994 DGL_TexCoord2f(0, .75f, .5f);
995 DGL_Vertex2f(v1[0], v1[1]);
996
997 DGL_TexCoord2f(0, .75f, .5f);
998 DGL_Vertex2f(v2[0], v2[1]);
999
1000 DGL_TexCoord2f(0, .75f, 1);
1001 DGL_Vertex2f(v3[0], v3[1]);
1002
1003 DGL_TexCoord2f(0, .75f, 1);
1004 DGL_Vertex2f(v4[0], v4[1]);
1005 DGL_End();
1006 }
1007
1008 if(caps)
1009 {
1010 float v1[2], v2[2], v3[2], v4[2];
1011
1012 v1[0] = b[0] + normal[0] * thickness;
1013 v1[1] = b[1] + normal[1] * thickness;
1014 v2[0] = b[0] + unit[0] * thickness + normal[0] * thickness;
1015 v2[1] = b[1] + unit[1] * thickness + normal[1] * thickness;
1016 v3[0] = b[0] + unit[0] * thickness - normal[0] * thickness;
1017 v3[1] = b[1] + unit[1] * thickness - normal[1] * thickness;
1018 v4[0] = b[0] - normal[0] * thickness;
1019 v4[1] = b[1] - normal[1] * thickness;
1020
1021 DGL_Bind(tex);
1022 DGL_Color4f(red, green, blue, alpha);
1023
1024 DGL_Begin(DGL_QUADS);
1025 DGL_TexCoord2f(0, .5f, 0);
1026 DGL_Vertex2f(v1[0], v1[1]);
1027
1028 DGL_TexCoord2f(0, 1, 0);
1029 DGL_Vertex2f(v2[0], v2[1]);
1030
1031 DGL_TexCoord2f(0, 1, 1);
1032 DGL_Vertex2f(v3[0], v3[1]);
1033
1034 DGL_TexCoord2f(0, .5, 1);
1035 DGL_Vertex2f(v4[0], v4[1]);
1036 DGL_End();
1037 }
1038 }
1039
M_DrawTextFragmentShadowed(const char * string,int x,int y,int alignFlags,short textFlags,float r,float g,float b,float a)1040 void M_DrawTextFragmentShadowed(const char* string, int x, int y, int alignFlags,
1041 short textFlags, float r, float g, float b, float a)
1042 {
1043 FR_SetColorAndAlpha(0, 0, 0, a * .4f);
1044 FR_DrawTextXY3(string, x+2, y+2, alignFlags, textFlags);
1045
1046 FR_SetColorAndAlpha(r, g, b, a);
1047 FR_DrawTextXY3(string, x, y, alignFlags, textFlags);
1048 }
1049
patchReplacement(patchid_t patchId)1050 static char const *patchReplacement(patchid_t patchId)
1051 {
1052 dint idx = patchReplacementValueIndex(patchId);
1053 if(idx == -1) return nullptr;
1054 if(idx >= 0 && idx < Defs().values.size()) return Defs().values[idx].text;
1055 throw Error("Hu_FindPatchReplacementString", "Failed retrieving text value #" + String::number(idx));
1056 }
1057
Hu_FindPatchReplacementString(patchid_t patchId,int flags)1058 char const *Hu_FindPatchReplacementString(patchid_t patchId, int flags)
1059 {
1060 char const *replacement = patchReplacement(patchId);
1061 if(flags & (PRF_NO_IWAD | PRF_NO_PWAD))
1062 {
1063 patchinfo_t info;
1064 R_GetPatchInfo(patchId, &info);
1065 if(!info.flags.isCustom)
1066 {
1067 if(flags & PRF_NO_IWAD)
1068 return nullptr;
1069 }
1070 else
1071 {
1072 if(flags & PRF_NO_PWAD)
1073 return nullptr;
1074 }
1075 }
1076 return replacement;
1077 }
1078
Hu_ChoosePatchReplacement(patchreplacemode_t mode,patchid_t patchId,de::String const & text)1079 de::String Hu_ChoosePatchReplacement(patchreplacemode_t mode, patchid_t patchId, de::String const &text)
1080 {
1081 if(mode != PRM_NONE)
1082 {
1083 // We might be able to replace the patch with a string replacement.
1084 if(patchId != 0)
1085 {
1086 patchinfo_t info;
1087 R_GetPatchInfo(patchId, &info);
1088 if(!info.flags.isCustom)
1089 {
1090 if(text.isEmpty())
1091 {
1092 // Look for a user replacement.
1093 return de::String(Hu_FindPatchReplacementString(patchId, PRF_NO_PWAD));
1094 }
1095
1096 return text;
1097 }
1098 }
1099 else
1100 {
1101 return text;
1102 }
1103 }
1104
1105 return ""; // No replacement available/wanted.
1106 }
1107
WI_DrawPatch(patchid_t patchId,de::String const & replacement,de::Vector2i const & origin,int alignFlags,int patchFlags,short textFlags)1108 void WI_DrawPatch(patchid_t patchId, de::String const &replacement, de::Vector2i const &origin,
1109 int alignFlags, int patchFlags, short textFlags)
1110 {
1111 if(!replacement.isEmpty())
1112 {
1113 // Use the replacement string.
1114 const Point2Raw originAsPoint2Raw = {{{origin.x, origin.y}}};
1115 FR_DrawText3(replacement.toUtf8().constData(), &originAsPoint2Raw, alignFlags, textFlags);
1116 return;
1117 }
1118 // Use the original patch.
1119 GL_DrawPatch(patchId, origin, alignFlags, patchFlags);
1120 }
1121
Draw_BeginZoom(float s,float originX,float originY)1122 void Draw_BeginZoom(float s, float originX, float originY)
1123 {
1124 DGL_MatrixMode(DGL_MODELVIEW);
1125 DGL_PushMatrix();
1126
1127 DGL_Translatef(originX, originY, 0);
1128 DGL_Scalef(s, s, 1);
1129 DGL_Translatef(-originX, -originY, 0);
1130 }
1131
Draw_EndZoom(void)1132 void Draw_EndZoom(void)
1133 {
1134 DGL_MatrixMode(DGL_MODELVIEW);
1135 DGL_PopMatrix();
1136 }
1137
1138 /**
1139 * Draws a 'fancy' fullscreen fog effect. Used as a background to various
1140 * HUD displays.
1141 */
Hu_DrawFogEffect(int effectID,DGLuint tex,float texOffset[2],float texAngle,float alpha,float arg1)1142 void Hu_DrawFogEffect(int effectID, DGLuint tex, float texOffset[2],
1143 float texAngle, float alpha, float arg1)
1144 {
1145 const float xscale = 2.0f;
1146 const float yscale = 1.0f;
1147
1148 if(!(alpha > 0))
1149 return;
1150
1151 if(effectID == 4)
1152 {
1153 DGL_SetNoMaterial();
1154 DGL_DrawRectf2Color(0, 0, 320, 200, 0.0f, 0.0f, 0.0f, MIN_OF(alpha, .5f));
1155 return;
1156 }
1157
1158 if(effectID == 2)
1159 {
1160 DGL_Color4f(alpha, alpha / 2, 0, alpha / 3);
1161 DGL_BlendMode(BM_INVERSE_MUL);
1162 DGL_DrawRectf2Tiled(0, 0, 320, 200, 1, 1);
1163 }
1164
1165 DGL_Bind(tex);
1166 if(tex)
1167 DGL_Enable(DGL_TEXTURE_2D);
1168
1169 DGL_Color3f(alpha, alpha, alpha);
1170 DGL_MatrixMode(DGL_TEXTURE);
1171 DGL_LoadIdentity();
1172 DGL_PushMatrix();
1173
1174 if(effectID == 1)
1175 {
1176 DGL_Color3f(alpha / 3, alpha / 2, alpha / 2);
1177 DGL_BlendMode(BM_INVERSE_MUL);
1178 }
1179 else if(effectID == 2)
1180 {
1181 DGL_Color3f(alpha / 5, alpha / 3, alpha / 2);
1182 DGL_BlendFunc(DGL_SRC_ALPHA, DGL_SRC_ALPHA);
1183 }
1184 else if(effectID == 0)
1185 {
1186 DGL_Color3f(alpha * 0.15, alpha * 0.2, alpha * 0.3);
1187 DGL_BlendFunc(DGL_SRC_ALPHA, DGL_SRC_ALPHA);
1188 }
1189
1190 if(effectID == 3)
1191 { // The fancy one.
1192 DGL_BlendFunc(DGL_SRC_ALPHA, DGL_SRC_ALPHA);
1193
1194 DGL_Translatef(texOffset[VX] / 320, texOffset[VY] / 200, 0);
1195 DGL_Rotatef(texAngle * 1, 0, 0, 1);
1196 DGL_Translatef(-texOffset[VX] / 320, -texOffset[VY] / 200, 0);
1197
1198 DGL_Begin(DGL_QUADS);
1199 // Top Half
1200 DGL_Color4f(alpha * 0.25, alpha * 0.3, alpha * 0.4, 1 - (alpha * 0.8) );
1201 DGL_TexCoord2f(0, 0, 0);
1202 DGL_Vertex2f(0, 0);
1203
1204 DGL_Color4f(alpha * 0.25, alpha * 0.3, alpha * 0.4, 1 - (alpha * 0.8) );
1205 DGL_TexCoord2f(0, xscale, 0);
1206 DGL_Vertex2f(320, 0);
1207
1208 DGL_Color4f(alpha * 0.7, alpha * 0.7, alpha * 0.8, 1 - (0-(alpha * 0.9)));
1209 DGL_TexCoord2f(0, xscale, yscale * arg1);
1210 DGL_Vertex2f(320, 200 * arg1);
1211
1212 DGL_Color4f(alpha * 0.7, alpha * 0.7, alpha * 0.8, 1 - (0-(alpha * 0.9)));
1213 DGL_TexCoord2f(0, 0, yscale * arg1);
1214 DGL_Vertex2f(0, 200 * arg1);
1215
1216 // Bottom Half
1217 DGL_Color4f(alpha * 0.7, alpha * 0.7, alpha * 0.8, 1 - (0-(alpha * 0.9)));
1218 DGL_TexCoord2f(0, 0, yscale * arg1);
1219 DGL_Vertex2f(0, 200 * arg1);
1220
1221 DGL_Color4f(alpha * 0.7, alpha * 0.7, alpha * 0.8, 1 - (0-(alpha * 0.9)));
1222 DGL_TexCoord2f(0, xscale, yscale * arg1);
1223 DGL_Vertex2f(320, 200 * arg1);
1224
1225 DGL_Color4f(alpha * 0.25, alpha * 0.3, alpha * 0.4, 1 - (alpha * 0.8) );
1226 DGL_TexCoord2f(0, xscale, yscale);
1227 DGL_Vertex2f(320, 200);
1228
1229 DGL_Color4f(alpha * 0.25, alpha * 0.3, alpha * 0.4, 1 - (alpha * 0.8) );
1230 DGL_TexCoord2f(0, 0, yscale);
1231 DGL_Vertex2f(0, 200);
1232 DGL_End();
1233 }
1234 else
1235 {
1236 DGL_Translatef(texOffset[VX] / 320, texOffset[VY] / 200, 0);
1237 DGL_Rotatef(texAngle * (effectID == 0 ? 0.5 : 1), 0, 0, 1);
1238 DGL_Translatef(-texOffset[VX] / 320, -texOffset[VY] / 200, 0);
1239 if(effectID == 2)
1240 DGL_DrawRectf2Tiled(0, 0, 320, 200, 270 / 8, 4 * 225);
1241 else if(effectID == 0)
1242 DGL_DrawRectf2Tiled(0, 0, 320, 200, 270 / 4, 8 * 225);
1243 else
1244 DGL_DrawRectf2Tiled(0, 0, 320, 200, 270, 225);
1245 }
1246
1247 DGL_MatrixMode(DGL_TEXTURE);
1248 DGL_PopMatrix();
1249
1250 if(tex)
1251 DGL_Disable(DGL_TEXTURE_2D);
1252 DGL_BlendMode(BM_NORMAL);
1253 }
1254
drawFogEffect(void)1255 static void drawFogEffect(void)
1256 {
1257 #define mfd (&fogEffectData)
1258
1259 DGL_MatrixMode(DGL_MODELVIEW);
1260 DGL_PushMatrix();
1261
1262 // Two layers.
1263 Hu_DrawFogEffect(cfg.common.hudFog - 1, mfd->texture,
1264 mfd->layers[0].texOffset, mfd->layers[0].texAngle,
1265 mfd->alpha, fogEffectData.joinY);
1266 Hu_DrawFogEffect(cfg.common.hudFog - 1, mfd->texture,
1267 mfd->layers[1].texOffset, mfd->layers[1].texAngle,
1268 mfd->alpha, fogEffectData.joinY);
1269
1270 // Restore original matrices.
1271 DGL_MatrixMode(DGL_MODELVIEW);
1272 DGL_PopMatrix();
1273
1274 #undef mfd
1275 }
1276
Hu_Drawer()1277 void Hu_Drawer()
1278 {
1279 bool const menuOrMessageVisible = (Hu_MenuIsVisible() || Hu_IsMessageActive());
1280 bool const pauseGraphicVisible = Pause_IsUserPaused() && !FI_StackActive();
1281
1282 if(!menuOrMessageVisible && !pauseGraphicVisible)
1283 return;
1284
1285 if(pauseGraphicVisible)
1286 {
1287 const int winWidth = Get(DD_WINDOW_WIDTH);
1288 const int winHeight = Get(DD_WINDOW_HEIGHT);
1289
1290 /**
1291 * Use an orthographic projection in native screenspace. Then
1292 * translate and scale the projection to produce an aspect
1293 * corrected coordinate space at 4:3, aligned vertically to
1294 * the top and centered horizontally in the window.
1295 */
1296 DGL_MatrixMode(DGL_PROJECTION);
1297 DGL_PushMatrix();
1298 DGL_LoadIdentity();
1299 DGL_Ortho(0, 0, winWidth, winHeight, -1, 1);
1300
1301 DGL_Translatef((float)winWidth/2, (float)winHeight/SCREENHEIGHT * 4, 0);
1302 if(winWidth >= winHeight)
1303 DGL_Scalef((float)winHeight/SCREENHEIGHT, (float)winHeight/SCREENHEIGHT, 1);
1304 else
1305 DGL_Scalef((float)winWidth/SCREENWIDTH, (float)winWidth/SCREENWIDTH, 1);
1306
1307 DGL_Enable(DGL_TEXTURE_2D);
1308 DGL_Color4f(1, 1, 1, 1);
1309 FR_SetFont(FID(GF_FONTB));
1310 FR_LoadDefaultAttrib();
1311 FR_SetLeading(0);
1312
1313 WI_DrawPatch(m_pause, Hu_ChoosePatchReplacement(PRM_ALLOW_TEXT, m_pause),
1314 de::Vector2i(), ALIGN_TOP, DPF_NO_OFFSET, 0);
1315
1316 DGL_Disable(DGL_TEXTURE_2D);
1317
1318 DGL_MatrixMode(DGL_PROJECTION);
1319 DGL_PopMatrix();
1320 }
1321
1322 if(!menuOrMessageVisible)
1323 return;
1324
1325 // Draw the fog effect?
1326 if(fogEffectData.alpha > 0 && cfg.common.hudFog)
1327 drawFogEffect();
1328
1329 if(Hu_IsMessageActive())
1330 {
1331 Hu_MsgDrawer();
1332 }
1333 else
1334 {
1335 Hu_MenuDrawer();
1336 }
1337 }
1338
Hu_FogEffectSetAlphaTarget(float alpha)1339 void Hu_FogEffectSetAlphaTarget(float alpha)
1340 {
1341 fogEffectData.targetAlpha = MINMAX_OF(0, alpha, 1);
1342 }
1343
Hu_IsStatusBarVisible(int player)1344 dd_bool Hu_IsStatusBarVisible(int player)
1345 {
1346 #ifdef __JDOOM64__
1347 DENG_UNUSED(player);
1348 return false;
1349 #else
1350 if(!ST_StatusBarIsActive(player)) return false;
1351
1352 if(ST_AutomapIsOpen(player) && cfg.common.automapHudDisplay == 0)
1353 {
1354 return false;
1355 }
1356
1357 return true;
1358 #endif
1359 }
1360
1361 #if __JDOOM__ || __JDOOM64__
Hu_MapTitleFirstLineHeight()1362 int Hu_MapTitleFirstLineHeight()
1363 {
1364 int y = 0;
1365 de::Uri titleImage = G_MapTitleImage(gfw_Session()->mapUri());
1366 if(!titleImage.isEmpty())
1367 {
1368 if(!titleImage.scheme().compareWithoutCase("Patches"))
1369 {
1370 patchinfo_t info;
1371 patchid_t patchId = R_DeclarePatch(titleImage.path().toUtf8().constData());
1372 if(R_GetPatchInfo(patchId, &info))
1373 {
1374 y = info.geometry.size.height + 2;
1375 }
1376 }
1377 }
1378 return de::max(14, y);
1379 }
1380 #endif
1381
Hu_IsMapTitleAuthorVisible()1382 dd_bool Hu_IsMapTitleAuthorVisible()
1383 {
1384 de::String const author = G_MapAuthor(gfw_Session()->mapUri(), CPP_BOOL(cfg.common.hideIWADAuthor));
1385 return !author.isEmpty() && (actualMapTime <= 6 * TICSPERSEC);
1386 }
1387
Hu_MapTitleHeight(void)1388 int Hu_MapTitleHeight(void)
1389 {
1390 int h = (Hu_IsMapTitleAuthorVisible()? 8 : 0);
1391
1392 #if __JDOOM__ || __JDOOM64__
1393 return Hu_MapTitleFirstLineHeight() + h;
1394 #endif
1395
1396 #if __JHERETIC__ || __JHEXEN__
1397 return 20 + h;
1398 #endif
1399 }
1400
Hu_DrawMapTitle(float alpha,dd_bool mapIdInsteadOfAuthor)1401 void Hu_DrawMapTitle(float alpha, dd_bool mapIdInsteadOfAuthor)
1402 {
1403 de::Uri const mapUri = gfw_Session()->mapUri();
1404 de::String const title = G_MapTitle(mapUri);
1405 de::String const author = G_MapAuthor(mapUri, CPP_BOOL(cfg.common.hideIWADAuthor));
1406
1407 float y = 0;
1408
1409 DGL_Enable(DGL_TEXTURE_2D);
1410 DGL_Color4f(1, 1, 1, alpha);
1411
1412 FR_SetFont(FID(GF_FONTB));
1413 FR_LoadDefaultAttrib();
1414 FR_SetColorAndAlpha(defFontRGB[0], defFontRGB[1], defFontRGB[2], alpha);
1415
1416 #if __JDOOM__ || __JDOOM64__
1417 patchid_t patchId = 0;
1418 de::Uri const titleImage = G_MapTitleImage(mapUri);
1419 if(!titleImage.isEmpty())
1420 {
1421 if(!titleImage.scheme().compareWithoutCase("Patches"))
1422 {
1423 patchId = R_DeclarePatch(titleImage.path().toUtf8().constData());
1424 }
1425 }
1426 WI_DrawPatch(patchId, Hu_ChoosePatchReplacement(PRM_ALLOW_TEXT, patchId, title),
1427 de::Vector2i(), ALIGN_TOP, 0, DTF_ONLY_SHADOW);
1428
1429 // Following line of text placed according to patch height.
1430 y += Hu_MapTitleFirstLineHeight();
1431
1432 #elif __JHERETIC__ || __JHEXEN__
1433 if(!title.isEmpty())
1434 {
1435 FR_DrawTextXY3(title.toUtf8().constData(), 0, 0, ALIGN_TOP, DTF_ONLY_SHADOW);
1436 y += 20;
1437 }
1438 #endif
1439
1440 if(mapIdInsteadOfAuthor)
1441 {
1442 FR_SetFont(FID(GF_FONTA));
1443 #if defined(__JHERETIC__) || defined(__JHEXEN__)
1444 FR_SetColorAndAlpha(.85f, .85f, .85f, alpha);
1445 #else
1446 FR_SetColorAndAlpha(.6f, .6f, .6f, alpha);
1447 #endif
1448 FR_DrawTextXY3(mapUri.path().toUtf8().constData(), 0, y, ALIGN_TOP, DTF_ONLY_SHADOW);
1449 }
1450 else if(!author.isEmpty())
1451 {
1452 FR_SetFont(FID(GF_FONTA));
1453 FR_SetColorAndAlpha(.5f, .5f, .5f, alpha);
1454 FR_DrawTextXY3(author.toUtf8().constData(), 0, y, ALIGN_TOP, DTF_ONLY_SHADOW);
1455 }
1456
1457 DGL_Disable(DGL_TEXTURE_2D);
1458 }
1459
Hu_IsMapTitleVisible(void)1460 dd_bool Hu_IsMapTitleVisible(void)
1461 {
1462 if(!cfg.common.mapTitle) return false;
1463
1464 return (actualMapTime < 6 * 35) || ST_AutomapIsOpen(DISPLAYPLAYER);
1465 }
1466
needToRespectStatusBarHeightWhenAutomapOpen(void)1467 static dd_bool needToRespectStatusBarHeightWhenAutomapOpen(void)
1468 {
1469 #ifndef __JDOOM64__
1470 return Hu_IsStatusBarVisible(DISPLAYPLAYER);
1471 #endif
1472
1473 return false;
1474 }
1475
needToRespectHudSizeWhenAutomapOpen(void)1476 static dd_bool needToRespectHudSizeWhenAutomapOpen(void)
1477 {
1478 #ifdef __JDOOM__
1479 if(cfg.hudShown[HUD_FACE] && !Hu_IsStatusBarVisible(DISPLAYPLAYER) &&
1480 cfg.common.automapHudDisplay > 0) return true;
1481 #endif
1482 return false;
1483 }
1484
Hu_MapTitleDrawer(const RectRaw * portGeometry)1485 void Hu_MapTitleDrawer(const RectRaw* portGeometry)
1486 {
1487 if(!cfg.common.mapTitle || !portGeometry) return;
1488
1489 // Scale according to the viewport size.
1490 float scale;
1491 R_ChooseAlignModeAndScaleFactor(&scale, SCREENWIDTH, SCREENHEIGHT,
1492 portGeometry->size.width, portGeometry->size.height,
1493 scalemode_t(cfg.common.menuScaleMode));
1494
1495 // Determine origin of the title.
1496 Point2Raw origin = {{{portGeometry->size.width / 2,
1497 6 * portGeometry->size.height / SCREENHEIGHT}}};
1498
1499 // Should the title be positioned in the bottom of the view?
1500 if(cfg.common.automapTitleAtBottom &&
1501 ST_AutomapIsOpen(DISPLAYPLAYER) &&
1502 (actualMapTime > 6 * TICSPERSEC))
1503 {
1504 origin.y = portGeometry->size.height - 1.2f * Hu_MapTitleHeight() * scale;
1505
1506 #if __JHERETIC__ || __JHEXEN__
1507 if(Hu_InventoryIsOpen(DISPLAYPLAYER) && !Hu_IsStatusBarVisible(DISPLAYPLAYER))
1508 {
1509 // Omit the title altogether while the inventory is open.
1510 return;
1511 }
1512 #endif
1513 float off = 0; // in vanilla pixels
1514 if(needToRespectStatusBarHeightWhenAutomapOpen())
1515 {
1516 Size2Raw stBarSize;
1517 R_StatusBarSize(DISPLAYPLAYER, &stBarSize);
1518 off += stBarSize.height;
1519 }
1520 else if(needToRespectHudSizeWhenAutomapOpen())
1521 {
1522 off += 30 * cfg.common.hudScale;
1523 }
1524
1525 origin.y -= off * portGeometry->size.height / float(SCREENHEIGHT);
1526 }
1527
1528 DGL_MatrixMode(DGL_MODELVIEW);
1529 DGL_PushMatrix();
1530
1531 Point2Raw portOrigin;
1532 R_ViewPortOrigin(DISPLAYPLAYER, &portOrigin);
1533
1534 // After scaling, the title is centered horizontally on the screen.
1535 DGL_Translatef(portOrigin.x + origin.x, portOrigin.y + origin.y, 0);
1536
1537 DGL_Scalef(scale, scale * 1.2f/*aspect correct*/, 1);
1538
1539 // Level information is shown for a few seconds in the beginning of a level.
1540 if(actualMapTime <= 6 * TICSPERSEC)
1541 {
1542 // Fade the title in and out.
1543 float alpha = 1;
1544 if(actualMapTime < 35)
1545 alpha = actualMapTime / 35.0f;
1546 if(actualMapTime > 5 * 35)
1547 alpha = 1 - (actualMapTime - 5 * 35) / 35.0f;
1548
1549 // Make the title 3/4 smaller.
1550 DGL_Scalef(.75f, .75f, 1);
1551
1552 Hu_DrawMapTitle(alpha, false /* show author */);
1553 }
1554 else if(ST_AutomapIsOpen(DISPLAYPLAYER) && (actualMapTime > 6 * TICSPERSEC))
1555 {
1556 // When the automap is open, the title is displayed together with the
1557 // map identifier (URI).
1558
1559 // Fade the title in.
1560 float alpha = 1;
1561 if(actualMapTime < 7 * 35)
1562 alpha = MINMAX_OF(0, (actualMapTime - 6 * 35) / 35.f, 1);
1563
1564 DGL_Scalef(.5f, .5f, 1);
1565
1566 Hu_DrawMapTitle(alpha, true /* show map ID */);
1567 }
1568
1569 DGL_MatrixMode(DGL_MODELVIEW);
1570 DGL_PopMatrix();
1571 }
1572
M_DrawShadowedPatch3(patchid_t id,int x,int y,int alignFlags,int patchFlags,float r,float g,float b,float a)1573 void M_DrawShadowedPatch3(patchid_t id, int x, int y, int alignFlags, int patchFlags,
1574 float r, float g, float b, float a)
1575 {
1576 if(id == 0 || DD_GetInteger(DD_NOVIDEO))
1577 return;
1578
1579 DGL_Color4f(0, 0, 0, a * .4f);
1580 GL_DrawPatch(id, Vector2i(x + 2, y + 2), alignFlags, patchFlags);
1581
1582 DGL_Color4f(r, g, b, a);
1583 GL_DrawPatch(id, Vector2i(x, y), alignFlags, patchFlags);
1584 }
1585
M_DrawShadowedPatch2(patchid_t id,int x,int y,int alignFlags,int patchFlags)1586 void M_DrawShadowedPatch2(patchid_t id, int x, int y, int alignFlags, int patchFlags)
1587 {
1588 M_DrawShadowedPatch3(id, x, y, alignFlags, patchFlags, 1, 1, 1, 1);
1589 }
1590
M_DrawShadowedPatch(patchid_t id,int x,int y)1591 void M_DrawShadowedPatch(patchid_t id, int x, int y)
1592 {
1593 M_DrawShadowedPatch2(id, x, y, ALIGN_TOPLEFT, 0);
1594 }
1595