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