1 /** @file st_stuff.cpp  DOOM specific statusbar and misc HUD widgets.
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2006-2014 Daniel Swanson <danij@dengine.net>
5  * @authors Copyright © 1993-1996 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 #if defined(WIN32) && defined(_MSC_VER)
23 // Something in here is incompatible with MSVC 2010 optimization.
24 // Symptom: automap not visible.
25 #  pragma optimize("", off)
26 #  pragma warning(disable : 4748)
27 #endif
28 
29 #include <cstdio>
30 #include <cstdlib>
31 #include <cstring>
32 
33 #include "jdoom.h"
34 
35 #include "dmu_lib.h"
36 #include "d_net.h"
37 #include "d_netsv.h"
38 #include "hu_stuff.h"
39 #include "hu_lib.h"
40 #include "hud/automapstyle.h"
41 #include "p_mapsetup.h"
42 #include "p_tick.h"
43 #include "player.h"
44 #include "p_user.h"
45 #include "r_common.h"
46 
47 #include "hud/widgets/armorwidget.h"
48 #include "hud/widgets/automapwidget.h"
49 #include "hud/widgets/chatwidget.h"
50 #include "hud/widgets/fragswidget.h"
51 #include "hud/widgets/groupwidget.h"
52 #include "hud/widgets/healthwidget.h"
53 #include "hud/widgets/itemswidget.h"
54 #include "hud/widgets/keyslotwidget.h"
55 #include "hud/widgets/keyswidget.h"
56 #include "hud/widgets/killswidget.h"
57 #include "hud/widgets/playerlogwidget.h"
58 #include "hud/widgets/readyammowidget.h"
59 #include "hud/widgets/readyammoiconwidget.h"
60 #include "hud/widgets/secretswidget.h"
61 
62 #include "hud/widgets/ammowidget.h"
63 #include "hud/widgets/armoriconwidget.h"
64 #include "hud/widgets/facewidget.h"
65 #include "hud/widgets/healthiconwidget.h"
66 #include "hud/widgets/maxammowidget.h"
67 #include "hud/widgets/weaponslotwidget.h"
68 
69 using namespace de;
70 
71 enum
72 {
73     UWG_STATUSBAR,
74     UWG_MAPNAME,
75     UWG_BOTTOM,
76     UWG_BOTTOMLEFT,
77     UWG_BOTTOMLEFT2,
78     UWG_BOTTOMRIGHT,
79     UWG_BOTTOMCENTER,
80     UWG_TOPCENTER,
81     UWG_COUNTERS,
82     UWG_AUTOMAP,
83     NUM_UIWIDGET_GROUPS
84 };
85 
86 struct hudstate_t
87 {
88     dd_bool inited;
89     dd_bool stopped;
90     int hideTics;
91     float hideAmount;
92     float alpha;              ///< Fullscreen hud alpha value.
93     float showBar;            ///< Slide statusbar amount 1.0 is fully open.
94     dd_bool statusbarActive;  ///< Whether the statusbar is active.
95     int automapCheatLevel;    ///< @todo Belongs in player state?
96 
97     uiwidgetid_t groupIds[NUM_UIWIDGET_GROUPS];
98 
99     // Statusbar:
100     uiwidgetid_t sbarHealthId;
101     uiwidgetid_t sbarReadyammoId;
102     uiwidgetid_t sbarAmmoIds[NUM_AMMO_TYPES];
103     uiwidgetid_t sbarMaxammoIds[NUM_AMMO_TYPES];
104     uiwidgetid_t sbarWeaponslotIds[6];
105     uiwidgetid_t sbarArmorId;
106     uiwidgetid_t sbarFragsId;
107     uiwidgetid_t sbarKeyslotIds[3];
108     uiwidgetid_t sbarFaceId;
109 
110     // Fullscreen:
111     uiwidgetid_t healthId;
112     uiwidgetid_t healthiconId;
113     uiwidgetid_t armoriconId;
114     uiwidgetid_t keysId;
115     uiwidgetid_t armorId;
116     uiwidgetid_t readyammoiconId;
117     uiwidgetid_t readyammoId;
118     uiwidgetid_t faceId;
119     uiwidgetid_t fragsId;
120 
121     // Other:
122     uiwidgetid_t automapId;
123     uiwidgetid_t chatId;
124     uiwidgetid_t logId;
125     uiwidgetid_t secretsId;
126     uiwidgetid_t itemsId;
127     uiwidgetid_t killsId;
128 };
129 
130 static hudstate_t hudStates[MAXPLAYERS];
131 
132 static patchid_t pStatusbar;
133 static patchid_t pArmsBackground;
134 static patchid_t pFaceBackground[NUMTEAMS];
135 
SBarBackground_Drawer(HudWidget * wi,Point2Raw const * offset)136 void SBarBackground_Drawer(HudWidget *wi, Point2Raw const *offset)
137 {
138 #define WIDTH           ( ST_WIDTH)
139 #define HEIGHT          ( ST_HEIGHT)
140 #define X_OFFSET        ( 104 )
141 #define Y_OFFSET        (   1 )
142 #define ORIGINX         ( int(-WIDTH / 2) )
143 #define ORIGINY         ( int(-HEIGHT * ST_StatusBarShown(wi->player())) )
144 
145 #define FACE_X_OFFSET   ( 144 )
146 
147     float x = ORIGINX, y  = ORIGINY, w = WIDTH, h = HEIGHT;
148 
149     int const activeHud     = ST_ActiveHud(wi->player());
150     //float const textOpacity = (fullscreen == 0? 1 : uiRendState->pageAlpha * cfg.common.statusbarOpacity);
151     float const iconOpacity = (activeHud == 0? 1 : uiRendState->pageAlpha * cfg.common.statusbarOpacity);
152 
153     float cw, cw2, ch;
154 
155     if(ST_AutomapIsOpen(wi->player()) && cfg.common.automapHudDisplay == 0) return;
156     if(P_MobjIsCamera(players[wi->player()].plr->mo) && Get(DD_PLAYBACK)) return;
157 
158     float armsBGX    = 0;
159     dd_bool haveArms = false;
160     patchinfo_t armsInfo;
161     if(!gfw_Rule(deathmatch))
162     {
163         haveArms = R_GetPatchInfo(pArmsBackground, &armsInfo);
164 
165         // Do not cut out the arms area if the graphic is "empty" (no color info).
166         if(haveArms && armsInfo.flags.isEmpty)
167             haveArms = false;
168 
169         if(haveArms)
170         {
171             armsBGX = X_OFFSET + armsInfo.geometry.origin.x;
172         }
173     }
174 
175     DGL_MatrixMode(DGL_MODELVIEW);
176     DGL_PushMatrix();
177     if(offset) DGL_Translatef(offset->x, offset->y, 0);
178     DGL_Scalef(cfg.common.statusbarScale, cfg.common.statusbarScale, 1);
179 
180     DGL_SetPatch(pStatusbar, DGL_CLAMP_TO_EDGE, DGL_CLAMP_TO_EDGE);
181     DGL_Enable(DGL_TEXTURE_2D);
182     DGL_Color4f(1, 1, 1, iconOpacity);
183 
184     if(!(iconOpacity < 1))
185     {
186         // We can draw the full graphic in one go.
187         DGL_Begin(DGL_QUADS);
188             DGL_TexCoord2f(0, 0, 0);
189             DGL_Vertex2f(x, y);
190             DGL_TexCoord2f(0, 1, 0);
191             DGL_Vertex2f(x + w, y);
192             DGL_TexCoord2f(0, 1, 1);
193             DGL_Vertex2f(x + w, y + h);
194             DGL_TexCoord2f(0, 0, 1);
195             DGL_Vertex2f(x, y + h);
196         DGL_End();
197     }
198     else
199     {
200         // Alpha blended status bar, we'll need to cut it up into smaller bits...
201         // Up to faceback or ST_ARMS.
202         w = haveArms? armsBGX : FACE_X_OFFSET;
203         h = HEIGHT;
204         cw = w / WIDTH;
205 
206         DGL_Begin(DGL_QUADS);
207             DGL_TexCoord2f(0, 0, 0);
208             DGL_Vertex2f(x, y);
209             DGL_TexCoord2f(0, cw, 0);
210             DGL_Vertex2f(x + w, y);
211             DGL_TexCoord2f(0, cw, 1);
212             DGL_Vertex2f(x + w, y + h);
213             DGL_TexCoord2f(0, 0, 1);
214             DGL_Vertex2f(x, y + h);
215 
216         if(IS_NETGAME)
217         {
218             // Fill in any gap left before the faceback due to small ARMS.
219             if(haveArms && armsBGX + armsInfo.geometry.size.width < FACE_X_OFFSET)
220             {
221                 int sectionWidth = armsBGX + armsInfo.geometry.size.width;
222                 x   = ORIGINX + sectionWidth;
223                 y   = ORIGINY;
224                 w   = FACE_X_OFFSET - armsBGX - armsInfo.geometry.size.width;
225                 h   = HEIGHT;
226                 cw  = (float)sectionWidth / WIDTH;
227                 cw2 = (sectionWidth + w) / WIDTH;
228 
229                 DGL_TexCoord2f(0, cw, 0);
230                 DGL_Vertex2f(x, y);
231                 DGL_TexCoord2f(0, cw2, 0);
232                 DGL_Vertex2f(x + w, y);
233                 DGL_TexCoord2f(0, cw2, 1);
234                 DGL_Vertex2f(x + w, y + h);
235                 DGL_TexCoord2f(0, cw, 1);
236                 DGL_Vertex2f(x, y + h);
237             }
238 
239             // Awkward, 2 pixel tall strip above faceback.
240             x   = ORIGINX + FACE_X_OFFSET;
241             y   = ORIGINY;
242             w   = WIDTH - FACE_X_OFFSET - 141 - 2;
243             h   = HEIGHT - 30;
244             cw  = (float)FACE_X_OFFSET / WIDTH;
245             cw2 = (float)(FACE_X_OFFSET + w) / WIDTH;
246             ch  = h / HEIGHT;
247 
248             DGL_TexCoord2f(0, cw, 0);
249             DGL_Vertex2f(x, y);
250             DGL_TexCoord2f(0, cw2, 0);
251             DGL_Vertex2f(x + w, y);
252             DGL_TexCoord2f(0, cw2, ch);
253             DGL_Vertex2f(x + w, y + h);
254             DGL_TexCoord2f(0, cw, ch);
255             DGL_Vertex2f(x, y + h);
256 
257             // Awkward, 1 pixel tall strip bellow faceback.
258             x   = ORIGINX + FACE_X_OFFSET;
259             y   = ORIGINY + (HEIGHT - 1);
260             w   = WIDTH - FACE_X_OFFSET - 141 - 2;
261             h   = HEIGHT - 31;
262             cw  = (float)FACE_X_OFFSET / WIDTH;
263             cw2 = (float)(FACE_X_OFFSET + w) / WIDTH;
264             ch  = (float)(HEIGHT - 1) / HEIGHT;
265 
266             DGL_TexCoord2f(0, cw, ch);
267             DGL_Vertex2f(x, y);
268             DGL_TexCoord2f(0, cw2, ch);
269             DGL_Vertex2f(x + w, y);
270             DGL_TexCoord2f(0, cw2, 1);
271             DGL_Vertex2f(x + w, y + h);
272             DGL_TexCoord2f(0, cw, 1);
273             DGL_Vertex2f(x, y + h);
274 
275             // After faceback.
276             {
277             int sectionWidth = FACE_X_OFFSET + (WIDTH - FACE_X_OFFSET - 141 - 2);
278             x  = ORIGINX + sectionWidth;
279             y  = ORIGINY;
280             w  = WIDTH - sectionWidth;
281             h  = HEIGHT;
282             cw = (float)sectionWidth / WIDTH;
283             }
284         }
285         else
286         {
287             // Including area behind the face status indicator.
288             int sectionWidth = (haveArms? armsBGX + armsInfo.geometry.size.width : FACE_X_OFFSET);
289             x  = ORIGINX + sectionWidth;
290             y  = ORIGINY;
291             w  = WIDTH - sectionWidth;
292             h  = HEIGHT;
293             cw = (float)sectionWidth / WIDTH;
294         }
295 
296             DGL_TexCoord2f(0, cw, 0);
297             DGL_Vertex2f(x, y);
298             DGL_TexCoord2f(0, 1, 0);
299             DGL_Vertex2f(x + w, y);
300             DGL_TexCoord2f(0, 1, 1);
301             DGL_Vertex2f(x + w, y + h);
302             DGL_TexCoord2f(0, cw, 1);
303             DGL_Vertex2f(x, y + h);
304         DGL_End();
305     }
306 
307     if(haveArms)
308     {
309         // Draw the ARMS background.
310         DGL_SetPatch(armsInfo.id, DGL_CLAMP_TO_EDGE, DGL_CLAMP_TO_EDGE);
311 
312         x = ORIGINX + armsBGX;
313         y = ORIGINY + armsInfo.geometry.origin.y;
314         w = armsInfo.geometry.size.width;
315         h = armsInfo.geometry.size.height;
316 
317         DGL_Begin(DGL_QUADS);
318             DGL_TexCoord2f(0, 0, 0);
319             DGL_Vertex2f(x, y);
320             DGL_TexCoord2f(0, 1, 0);
321             DGL_Vertex2f(x + w, y);
322             DGL_TexCoord2f(0, 1, 1);
323             DGL_Vertex2f(x + w, y + h);
324             DGL_TexCoord2f(0, 0, 1);
325             DGL_Vertex2f(x, y + h);
326         DGL_End();
327     }
328 
329     // Faceback?
330     patchinfo_t fbgInfo;
331     if(IS_NETGAME && R_GetPatchInfo(pFaceBackground[cfg.playerColor[wi->player() % MAXPLAYERS] % 4], &fbgInfo))
332     {
333         DGL_SetPatch(fbgInfo.id, DGL_CLAMP_TO_EDGE, DGL_CLAMP_TO_EDGE);
334 
335         x   = ORIGINX + FACE_X_OFFSET;
336         y   = ORIGINY + (HEIGHT - 30);
337         w   = WIDTH - FACE_X_OFFSET - 141 - 2;
338         h   = HEIGHT - 3;
339         cw  = 1.f / fbgInfo.geometry.size.width;
340         cw2 = ((float)fbgInfo.geometry.size.width  - 1) / fbgInfo.geometry.size.width;
341         ch  = ((float)fbgInfo.geometry.size.height - 1) / fbgInfo.geometry.size.height;
342 
343         DGL_Begin(DGL_QUADS);
344             DGL_TexCoord2f(0, cw, 0);
345             DGL_Vertex2f(x, y);
346             DGL_TexCoord2f(0, cw2, 0);
347             DGL_Vertex2f(x + w, y);
348             DGL_TexCoord2f(0, cw2, ch);
349             DGL_Vertex2f(x + w, y + h);
350             DGL_TexCoord2f(0, cw, ch);
351             DGL_Vertex2f(x, y + h);
352         DGL_End();
353     }
354 
355     DGL_Disable(DGL_TEXTURE_2D);
356     DGL_MatrixMode(DGL_MODELVIEW);
357     DGL_PopMatrix();
358 
359 #undef FACE_X_OFFSET
360 #undef ORIGINY
361 #undef ORIGINX
362 #undef Y_OFFSET
363 #undef X_OFFSET
364 #undef HEIGHT
365 #undef WIDTH
366 }
367 
SBarBackground_UpdateGeometry(HudWidget * wi)368 void SBarBackground_UpdateGeometry(HudWidget *wi)
369 {
370     DENG2_ASSERT(wi);
371 
372     Rect_SetWidthHeight(&wi->geometry(), 0, 0);
373 
374     if(ST_AutomapIsOpen(wi->player()) && cfg.common.automapHudDisplay == 0) return;
375     if(P_MobjIsCamera(players[wi->player()].plr->mo) && Get(DD_PLAYBACK)) return;
376 
377     Rect_SetWidthHeight(&wi->geometry(), ST_WIDTH  * cfg.common.statusbarScale,
378                                          ST_HEIGHT * cfg.common.statusbarScale);
379 }
380 
ST_ActiveHud(int)381 int ST_ActiveHud(int /*player*/)
382 {
383     return (cfg.common.screenBlocks < 10? 0 : cfg.common.screenBlocks - 10);
384 }
385 
ST_HUDUnHide(int localPlayer,hueevent_t ev)386 void ST_HUDUnHide(int localPlayer, hueevent_t ev)
387 {
388     if(localPlayer < 0 || localPlayer >= MAXPLAYERS)
389         return;
390 
391     if(ev < HUE_FORCE || ev > NUMHUDUNHIDEEVENTS)
392     {
393         DENG2_ASSERT(!"ST_HUDUnHide: Invalid event type");
394         return;
395     }
396 
397     player_t *plr = &players[localPlayer];
398     if(!plr->plr->inGame) return;
399 
400     if(ev == HUE_FORCE || cfg.hudUnHide[ev])
401     {
402         hudStates[localPlayer].hideTics = (cfg.common.hudTimer * TICSPERSEC);
403         hudStates[localPlayer].hideAmount = 0;
404     }
405 }
406 
ST_Responder(event_t * ev)407 int ST_Responder(event_t *ev)
408 {
409     for(int i = 0; i < MAXPLAYERS; ++i)
410     {
411         if(auto *chat = ST_TryFindChatWidget(i))
412         {
413             if(int eaten = chat->handleEvent(*ev))
414                 return eaten;
415         }
416     }
417     return false;
418 }
419 
ST_Ticker(timespan_t ticLength)420 void ST_Ticker(timespan_t ticLength)
421 {
422     dd_bool const isSharpTic = DD_IsSharpTick();
423 
424     for(int i = 0; i < MAXPLAYERS; ++i)
425     {
426         player_t *plr   = &players[i];
427         hudstate_t *hud = &hudStates[i];
428 
429         if(!plr->plr->inGame)
430             continue;
431 
432         // Either slide the statusbar in or fade out the fullscreen HUD.
433         if(hud->statusbarActive)
434         {
435             if(hud->alpha > 0.0f)
436             {
437                 hud->alpha -= 0.1f;
438             }
439             else if(hud->showBar < 1.0f)
440             {
441                 hud->showBar += 0.1f;
442             }
443         }
444         else
445         {
446             if(cfg.common.screenBlocks == 13)
447             {
448                 if(hud->alpha > 0.0f)
449                 {
450                     hud->alpha -= 0.1f;
451                 }
452             }
453             else
454             {
455                 if(hud->showBar > 0.0f)
456                 {
457                     hud->showBar -= 0.1f;
458                 }
459                 else if(hud->alpha < 1.0f)
460                 {
461                     hud->alpha += 0.1f;
462                 }
463             }
464         }
465 
466         // The following is restricted to fixed 35 Hz ticks.
467         if(isSharpTic && !Pause_IsPaused())
468         {
469             if(cfg.common.hudTimer == 0)
470             {
471                 hud->hideTics = hud->hideAmount = 0;
472             }
473             else
474             {
475                 if(hud->hideTics > 0)
476                     hud->hideTics--;
477                 if(hud->hideTics == 0 && cfg.common.hudTimer > 0 && hud->hideAmount < 1)
478                     hud->hideAmount += 0.1f;
479             }
480         }
481 
482         if(hud->inited)
483         {
484             for(int k = 0; k < NUM_UIWIDGET_GROUPS; ++k)
485             {
486                 GUI_FindWidgetById(hud->groupIds[k]).tick(ticLength);
487             }
488         }
489         else
490         {
491             if(hud->hideTics > 0)
492                 hud->hideTics--;
493             if(hud->hideTics == 0 && cfg.common.hudTimer > 0 && hud->hideAmount < 1)
494                 hud->hideAmount += 0.1f;
495         }
496     }
497 }
498 
drawUIWidgetsForPlayer(player_t * plr)499 static void drawUIWidgetsForPlayer(player_t *plr)
500 {
501     DENG2_ASSERT(plr);
502 
503 #define DISPLAY_BORDER      (2) /// Units in fixed 320x200 screen space.
504 
505     int const localPlayer   = plr - players;
506     int const displayMode = ST_ActiveHud(localPlayer);
507     hudstate_t *hud       = &hudStates[localPlayer];
508 
509     Size2Raw portSize;    R_ViewPortSize  (localPlayer, &portSize);
510     Point2Raw portOrigin; R_ViewPortOrigin(localPlayer, &portOrigin);
511 
512     // The automap is drawn in a viewport scaled coordinate space (of viewwindow dimensions).
513     HudWidget &aGroup = GUI_FindWidgetById(hud->groupIds[UWG_AUTOMAP]);
514     aGroup.setOpacity(ST_AutomapOpacity(localPlayer));
515     aGroup.setMaximumSize(portSize);
516     GUI_DrawWidgetXY(&aGroup, 0, 0);
517 
518     // The rest of the UI is drawn in a fixed 320x200 coordinate space.
519     // Determine scale factors.
520     float scale;
521     R_ChooseAlignModeAndScaleFactor(&scale, SCREENWIDTH, SCREENHEIGHT,
522         portSize.width, portSize.height, SCALEMODE_SMART_STRETCH);
523 
524     DGL_MatrixMode(DGL_MODELVIEW);
525     DGL_PushMatrix();
526     DGL_Translatef(portOrigin.x, portOrigin.y, 0);
527     DGL_Scalef(scale, scale, 1);
528 
529     if(hud->statusbarActive || (displayMode < 3 || hud->alpha > 0))
530     {
531         float opacity = /**@todo Kludge: clamp*/de::min(1.0f, hud->alpha)/**kludge end*/ * (1 - hud->hideAmount);
532         Size2Raw drawnSize;
533 
534         DGL_MatrixMode(DGL_MODELVIEW);
535         DGL_Scalef(1, 1.2f/*aspect correct*/, 1);
536 
537         RectRaw displayRegion;
538         displayRegion.origin.x = displayRegion.origin.y = 0;
539         displayRegion.size.width  = .5f + portSize.width  / scale;
540         displayRegion.size.height = .5f + portSize.height / (scale * 1.2f /*aspect correct*/);
541 
542         if(hud->statusbarActive)
543         {
544             float const statusbarOpacity = (1 - hud->hideAmount) * hud->showBar;
545 
546             HudWidget &sbGroup = GUI_FindWidgetById(hud->groupIds[UWG_STATUSBAR]);
547             sbGroup.setOpacity(statusbarOpacity);
548             sbGroup.setMaximumSize(displayRegion.size);
549 
550             GUI_DrawWidget(&sbGroup, &displayRegion.origin);
551 
552             Size2_Raw(Rect_Size(&sbGroup.geometry()), &drawnSize);
553         }
554 
555         displayRegion.origin.x += DISPLAY_BORDER;
556         displayRegion.origin.y += DISPLAY_BORDER;
557         displayRegion.size.width  -= DISPLAY_BORDER*2;
558         displayRegion.size.height -= DISPLAY_BORDER*2;
559 
560         if(!hud->statusbarActive)
561         {
562             HudWidget &bGroup = GUI_FindWidgetById(hud->groupIds[UWG_BOTTOM]);
563             bGroup.setOpacity(opacity);
564             bGroup.setMaximumSize(displayRegion.size);
565 
566             GUI_DrawWidget(&bGroup, &displayRegion.origin);
567 
568             Size2_Raw(Rect_Size(&bGroup.geometry()), &drawnSize);
569         }
570 
571         HudWidget &mnGroup = GUI_FindWidgetById(hud->groupIds[UWG_MAPNAME]);
572         mnGroup.setOpacity(ST_AutomapOpacity(localPlayer));
573         int availHeight = displayRegion.size.height - (drawnSize.height > 0 ? drawnSize.height : 0);
574         Size2Raw size = {{{displayRegion.size.width, availHeight}}};
575         mnGroup.setMaximumSize(size);
576 
577         GUI_DrawWidget(&mnGroup, &displayRegion.origin);
578 
579         // The other displays are always visible except when using the "no-hud" mode.
580         if(hud->statusbarActive || displayMode < 3)
581             opacity = 1.0f;
582 
583         HudWidget &tcGroup = GUI_FindWidgetById(hud->groupIds[UWG_TOPCENTER]);
584         tcGroup.setOpacity(opacity);
585         tcGroup.setMaximumSize(displayRegion.size);
586 
587         GUI_DrawWidget(&tcGroup, &displayRegion.origin);
588 
589         HudWidget &cGroup = GUI_FindWidgetById(hud->groupIds[UWG_COUNTERS]);
590         cGroup.setOpacity(opacity);
591         cGroup.setMaximumSize(displayRegion.size);
592 
593         GUI_DrawWidget(&cGroup, &displayRegion.origin);
594     }
595 
596     DGL_MatrixMode(DGL_MODELVIEW);
597     DGL_PopMatrix();
598 
599 #undef DISPLAY_BORDER
600 }
601 
ST_Drawer(int localPlayer)602 void ST_Drawer(int localPlayer)
603 {
604     if(localPlayer < 0 || localPlayer >= MAXPLAYERS)
605         return;
606 
607     if(!players[localPlayer].plr->inGame) return;
608 
609     R_UpdateViewFilter(localPlayer);
610 
611     hudstate_t *hud = &hudStates[localPlayer];
612     hud->statusbarActive = (ST_ActiveHud(localPlayer) < 2) || (ST_AutomapIsOpen(localPlayer) && (cfg.common.automapHudDisplay == 0 || cfg.common.automapHudDisplay == 2));
613 
614     drawUIWidgetsForPlayer(&players[localPlayer]);
615 }
616 
ST_StatusBarIsActive(int localPlayer)617 dd_bool ST_StatusBarIsActive(int localPlayer)
618 {
619     DENG2_ASSERT(localPlayer >= 0 && localPlayer < MAXPLAYERS);
620 
621     if(!players[localPlayer].plr->inGame) return false;
622 
623     return hudStates[localPlayer].statusbarActive;
624 }
625 
ST_StatusBarShown(int localPlayer)626 float ST_StatusBarShown(int localPlayer)
627 {
628     DENG2_ASSERT(localPlayer >= 0 && localPlayer < MAXPLAYERS);
629     return hudStates[localPlayer].showBar;
630 }
631 
ST_loadGraphics()632 void ST_loadGraphics()
633 {
634     pStatusbar      = R_DeclarePatch("STBAR");
635     pArmsBackground = R_DeclarePatch("STARMS");
636     // Colored backgrounds for each team.
637     char nameBuf[9];
638     for(dint i = 0; i < 4; ++i)
639     {
640         sprintf(nameBuf, "STFB%d", i);
641         pFaceBackground[i] = R_DeclarePatch(nameBuf);
642     }
643 
644     guidata_face_t::prepareAssets();
645     guidata_keyslot_t::prepareAssets();
646     guidata_weaponslot_t::prepareAssets();
647 }
648 
ST_loadData()649 void ST_loadData()
650 {
651     ST_loadGraphics();
652 }
653 
initData(hudstate_t * hud)654 static void initData(hudstate_t *hud)
655 {
656     DENG2_ASSERT(hud);
657 
658     hud->statusbarActive = true;
659     hud->stopped = true;
660     hud->showBar = 1;
661 
662     // Statusbar:
663     GUI_FindWidgetById(hud->sbarArmorId).as<guidata_armor_t>().reset();
664     GUI_FindWidgetById(hud->sbarFaceId).as<guidata_face_t>().reset();
665     GUI_FindWidgetById(hud->sbarFragsId).as<guidata_frags_t>().reset();
666     GUI_FindWidgetById(hud->sbarHealthId).as<guidata_health_t>().reset();
667     GUI_FindWidgetById(hud->sbarReadyammoId).as<guidata_readyammo_t>().reset();
668     for(dint i = 0; i < NUM_AMMO_TYPES; ++i)
669     {
670         GUI_FindWidgetById(hud->sbarAmmoIds[i]).as<guidata_ammo_t>()
671                 .setAmmoType(ammotype_t( i ))
672                 .reset();
673 
674         GUI_FindWidgetById(hud->sbarMaxammoIds[i]).as<guidata_maxammo_t>()
675                 .setAmmoType(ammotype_t( i ))
676                 .reset();
677     }
678     for(dint i = 0; i < 6/*num weapon slots*/; ++i)
679     {
680         GUI_FindWidgetById(hud->sbarWeaponslotIds[i]).as<guidata_weaponslot_t>()
681                 .setSlot(i)
682                 .reset();
683     }
684     for(dint i = 0; i < 3/*num key slots*/; ++i)
685     {
686         GUI_FindWidgetById(hud->sbarKeyslotIds[i]).as<guidata_keyslot_t>()
687                 .setSlot(i)
688                 .reset();
689     }
690 
691     // Fullscreen:
692     GUI_FindWidgetById(hud->healthId).as<guidata_health_t>().reset();
693     GUI_FindWidgetById(hud->armoriconId).as<guidata_armoricon_t>().reset();
694     GUI_FindWidgetById(hud->armorId).as<guidata_armor_t>().reset();
695     GUI_FindWidgetById(hud->readyammoiconId).as<guidata_readyammoicon_t>().reset();
696     GUI_FindWidgetById(hud->readyammoId).as<guidata_readyammo_t>().reset();
697     GUI_FindWidgetById(hud->keysId).as<guidata_keys_t>().reset();
698     GUI_FindWidgetById(hud->fragsId).as<guidata_frags_t>().reset();
699     GUI_FindWidgetById(hud->faceId).as<guidata_face_t>().reset();
700 
701     // Other:
702     GUI_FindWidgetById(hud->secretsId).as<guidata_secrets_t>().reset();
703     GUI_FindWidgetById(hud->itemsId).as<guidata_items_t>().reset();
704     GUI_FindWidgetById(hud->killsId).as<guidata_kills_t>().reset();
705 
706     GUI_FindWidgetById(hud->logId).as<PlayerLogWidget>().clear();
707 
708     ST_HUDUnHide(hud - hudStates, HUE_FORCE);
709 }
710 
setAutomapCheatLevel(AutomapWidget & automap,int level)711 static void setAutomapCheatLevel(AutomapWidget &automap, int level)
712 {
713     hudstate_t *hud = &hudStates[automap.player()];
714 
715     hud->automapCheatLevel = level;
716 
717     dint flags = automap.flags() & ~(AWF_SHOW_ALLLINES|AWF_SHOW_THINGS|AWF_SHOW_SPECIALLINES|AWF_SHOW_VERTEXES|AWF_SHOW_LINE_NORMALS);
718     if(hud->automapCheatLevel >= 1)
719         flags |= AWF_SHOW_ALLLINES;
720     if(hud->automapCheatLevel == 2)
721         flags |= AWF_SHOW_THINGS | AWF_SHOW_SPECIALLINES;
722     if(hud->automapCheatLevel > 2)
723         flags |= (AWF_SHOW_VERTEXES | AWF_SHOW_LINE_NORMALS);
724     automap.setFlags(flags);
725 }
726 
initAutomapForCurrentMap(AutomapWidget & automap)727 static void initAutomapForCurrentMap(AutomapWidget &automap)
728 {
729     hudstate_t *hud = &hudStates[automap.player()];
730 
731     automap.reset();
732 
733     AABoxd const *mapBounds = reinterpret_cast<AABoxd *>(DD_GetVariable(DD_MAP_BOUNDING_BOX));
734     automap.setMapBounds(mapBounds->minX, mapBounds->maxX, mapBounds->minY, mapBounds->maxY);
735 
736     AutomapStyle *style = automap.style();
737 
738     // Determine the obj view scale factors.
739     if(automap.cameraZoomMode())
740         automap.setScale(0);
741 
742     automap.clearAllPoints(true/*silent*/);
743 
744 #if !__JHEXEN__
745     if(gfw_Rule(skill) == SM_BABY && cfg.common.automapBabyKeys)
746     {
747         automap.setFlags(automap.flags() | AWF_SHOW_KEYS);
748     }
749 #endif
750 
751 #if __JDOOM__
752     if(!IS_NETGAME && hud->automapCheatLevel)
753         style->setObjectSvg(AMO_THINGPLAYER, VG_CHEATARROW);
754 #endif
755 
756     // Are we re-centering on a followed mobj?
757     if (mobj_t *mob = automap.followMobj())
758     {
759         automap.setCameraOrigin(Vector2d(mob->origin), true);
760     }
761 
762     if(IS_NETGAME)
763     {
764         setAutomapCheatLevel(automap, 0);
765     }
766 
767     automap.reveal(false);
768 
769     // Add all immediately visible lines.
770     for(int i = 0; i < numlines; ++i)
771     {
772         xline_t *xline = &xlines[i];
773         if(!(xline->flags & ML_MAPPED)) continue;
774 
775         P_SetLineAutomapVisibility(automap.player(), i, true);
776     }
777 }
778 
ST_Start(int localPlayer)779 void ST_Start(int localPlayer)
780 {
781     if(localPlayer < 0 || localPlayer >= MAXPLAYERS) return;
782     hudstate_t *hud = &hudStates[localPlayer];
783 
784     if(!hud->stopped)
785     {
786         ST_Stop(localPlayer);
787     }
788 
789     initData(hud);
790 
791     //
792     // Initialize widgets according to player preferences.
793     //
794 
795     HudWidget &tcGroup = GUI_FindWidgetById(hud->groupIds[UWG_TOPCENTER]);
796     int flags = tcGroup.alignment();
797     flags &= ~(ALIGN_LEFT|ALIGN_RIGHT);
798     if(cfg.common.msgAlign == 0)
799         flags |= ALIGN_LEFT;
800     else if(cfg.common.msgAlign == 2)
801         flags |= ALIGN_RIGHT;
802     tcGroup.setAlignment(flags);
803 
804     auto &automap = GUI_FindWidgetById(hud->automapId).as<AutomapWidget>();
805     // If the automap was left open; close it.
806     automap.open(false, true /*instantly*/);
807     initAutomapForCurrentMap(automap);
808     automap.setCameraRotationMode(CPP_BOOL(cfg.common.automapRotate));
809 
810     hud->stopped = false;
811 }
812 
ST_Stop(int localPlayer)813 void ST_Stop(int localPlayer)
814 {
815     if(localPlayer < 0 || localPlayer >= MAXPLAYERS) return;
816 
817     hudstate_t *hud = &hudStates[localPlayer];
818     if(hud->stopped) return;
819 
820     hud->stopped = true;
821 }
822 
makeGroupWidget(int groupFlags,int localPlayer,int alignFlags,order_t order,int padding)823 static HudWidget *makeGroupWidget(int groupFlags, int localPlayer, int alignFlags, order_t order, int padding)
824 {
825     auto *grp = new GroupWidget(localPlayer);
826     grp->setAlignment(alignFlags)
827         .setFont(1);
828 
829     grp->setFlags(groupFlags);
830     grp->setOrder(order);
831     grp->setPadding(padding);
832 
833     return grp;
834 }
835 
ST_BuildWidgets(int localPlayer)836 void ST_BuildWidgets(int localPlayer)
837 {
838 #define PADDING             (2) /// Units in fixed 320x200 screen space.
839 
840 struct uiwidgetgroupdef_t
841 {
842     int group;
843     int alignFlags;
844     order_t order;
845     int groupFlags;
846     int padding;
847 };
848 
849 struct uiwidgetdef_t
850 {
851     HudElementName type;
852     int alignFlags;
853     int group;
854     gamefontid_t fontIdx;
855     void (*updateGeometry) (HudWidget *ob);
856     void (*drawer) (HudWidget *ob, Point2Raw const *origin);
857     uiwidgetid_t *id;
858 };
859 
860     hudstate_t *hud = &hudStates[localPlayer];
861     uiwidgetgroupdef_t const widgetGroupDefs[] = {
862         { UWG_STATUSBAR,    ALIGN_BOTTOM,      ORDER_NONE, 0, 0 },
863         { UWG_MAPNAME,      ALIGN_BOTTOMLEFT,  ORDER_NONE, 0, 0 },
864         { UWG_BOTTOMLEFT,   ALIGN_BOTTOMLEFT,  ORDER_RIGHTTOLEFT, UWGF_VERTICAL, PADDING },
865         { UWG_BOTTOMLEFT2,  ALIGN_BOTTOMLEFT,  ORDER_LEFTTORIGHT, 0, PADDING },
866         { UWG_BOTTOMRIGHT,  ALIGN_BOTTOMRIGHT, ORDER_RIGHTTOLEFT, 0, PADDING },
867         { UWG_BOTTOMCENTER, ALIGN_BOTTOM,      ORDER_RIGHTTOLEFT, UWGF_VERTICAL, PADDING },
868         { UWG_BOTTOM,       ALIGN_BOTTOMLEFT,  ORDER_LEFTTORIGHT, 0, 0 },
869         { UWG_TOPCENTER,    ALIGN_TOPLEFT,     ORDER_LEFTTORIGHT, UWGF_VERTICAL, PADDING },
870         { UWG_COUNTERS,     ALIGN_LEFT,        ORDER_RIGHTTOLEFT, UWGF_VERTICAL, PADDING },
871         { UWG_AUTOMAP,      ALIGN_TOPLEFT,     ORDER_NONE, 0, 0 }
872     };
873     uiwidgetdef_t const widgetDefs[] = {
874         { GUI_BOX,      ALIGN_TOPLEFT,      UWG_STATUSBAR,      GF_NONE,    function_cast<UpdateGeometryFunc>(SBarBackground_UpdateGeometry), function_cast<DrawFunc>(SBarBackground_Drawer), nullptr },
875         { GUI_READYAMMO, ALIGN_TOPLEFT,     UWG_STATUSBAR,      GF_STATUS,  function_cast<UpdateGeometryFunc>(SBarReadyAmmo_UpdateGeometry), function_cast<DrawFunc>(SBarReadyAmmo_Drawer), &hud->sbarReadyammoId },
876         { GUI_HEALTH,   ALIGN_TOPLEFT,      UWG_STATUSBAR,      GF_STATUS,  function_cast<UpdateGeometryFunc>(SBarHealthWidget_UpdateGeometry), function_cast<DrawFunc>(SBarHealthWidget_Draw), &hud->sbarHealthId },
877         { GUI_WEAPONSLOT, ALIGN_TOPLEFT,    UWG_STATUSBAR,      GF_NONE,    nullptr, nullptr, &hud->sbarWeaponslotIds[0] },
878         { GUI_WEAPONSLOT, ALIGN_TOPLEFT,    UWG_STATUSBAR,      GF_NONE,    nullptr, nullptr, &hud->sbarWeaponslotIds[1] },
879         { GUI_WEAPONSLOT, ALIGN_TOPLEFT,    UWG_STATUSBAR,      GF_NONE,    nullptr, nullptr, &hud->sbarWeaponslotIds[2] },
880         { GUI_WEAPONSLOT, ALIGN_TOPLEFT,    UWG_STATUSBAR,      GF_NONE,    nullptr, nullptr, &hud->sbarWeaponslotIds[3] },
881         { GUI_WEAPONSLOT, ALIGN_TOPLEFT,    UWG_STATUSBAR,      GF_NONE,    nullptr, nullptr, &hud->sbarWeaponslotIds[4] },
882         { GUI_WEAPONSLOT, ALIGN_TOPLEFT,    UWG_STATUSBAR,      GF_NONE,    nullptr, nullptr, &hud->sbarWeaponslotIds[5] },
883         { GUI_FRAGS,    ALIGN_TOPLEFT,      UWG_STATUSBAR,      GF_STATUS,  function_cast<UpdateGeometryFunc>(SBarFragsWidget_UpdateGeometry), function_cast<DrawFunc>(SBarFragsWidget_Draw), &hud->sbarFragsId },
884         { GUI_FACE,     ALIGN_TOPLEFT,      UWG_STATUSBAR,      GF_NONE,    function_cast<UpdateGeometryFunc>(SBarFace_UpdateGeometry), function_cast<DrawFunc>(SBarFace_Drawer), &hud->sbarFaceId },
885         { GUI_ARMOR,    ALIGN_TOPLEFT,      UWG_STATUSBAR,      GF_STATUS,  function_cast<UpdateGeometryFunc>(SBarArmor_UpdateGeometry), function_cast<DrawFunc>(SBarArmorWidget_Draw), &hud->sbarArmorId },
886         { GUI_KEYSLOT,  ALIGN_TOPLEFT,      UWG_STATUSBAR,      GF_NONE,    nullptr, nullptr, &hud->sbarKeyslotIds[0] },
887         { GUI_KEYSLOT,  ALIGN_TOPLEFT,      UWG_STATUSBAR,      GF_NONE,    nullptr, nullptr, &hud->sbarKeyslotIds[1] },
888         { GUI_KEYSLOT,  ALIGN_TOPLEFT,      UWG_STATUSBAR,      GF_NONE,    nullptr, nullptr, &hud->sbarKeyslotIds[2] },
889         { GUI_AMMO,     ALIGN_TOPLEFT,      UWG_STATUSBAR,      GF_INDEX,   nullptr, nullptr, &hud->sbarAmmoIds[AT_CLIP] },
890         { GUI_AMMO,     ALIGN_TOPLEFT,      UWG_STATUSBAR,      GF_INDEX,   nullptr, nullptr, &hud->sbarAmmoIds[AT_SHELL] },
891         { GUI_AMMO,     ALIGN_TOPLEFT,      UWG_STATUSBAR,      GF_INDEX,   nullptr, nullptr, &hud->sbarAmmoIds[AT_CELL] },
892         { GUI_AMMO,     ALIGN_TOPLEFT,      UWG_STATUSBAR,      GF_INDEX,   nullptr, nullptr, &hud->sbarAmmoIds[AT_MISSILE] },
893         { GUI_MAXAMMO,  ALIGN_TOPLEFT,      UWG_STATUSBAR,      GF_INDEX,   nullptr, nullptr, &hud->sbarMaxammoIds[AT_CLIP] },
894         { GUI_MAXAMMO,  ALIGN_TOPLEFT,      UWG_STATUSBAR,      GF_INDEX,   nullptr, nullptr, &hud->sbarMaxammoIds[AT_SHELL] },
895         { GUI_MAXAMMO,  ALIGN_TOPLEFT,      UWG_STATUSBAR,      GF_INDEX,   nullptr, nullptr, &hud->sbarMaxammoIds[AT_CELL] },
896         { GUI_MAXAMMO,  ALIGN_TOPLEFT,      UWG_STATUSBAR,      GF_INDEX,   nullptr, nullptr, &hud->sbarMaxammoIds[AT_MISSILE] },
897         { GUI_HEALTHICON, ALIGN_BOTTOMLEFT, UWG_BOTTOMLEFT2,    GF_NONE,    nullptr, nullptr, &hud->healthiconId },
898         { GUI_HEALTH,   ALIGN_BOTTOMLEFT,   UWG_BOTTOMLEFT2,    GF_FONTB,   function_cast<UpdateGeometryFunc>(HealthWidget_UpdateGeometry), function_cast<DrawFunc>(HealthWidget_Draw), &hud->healthId },
899         { GUI_READYAMMOICON, ALIGN_BOTTOMLEFT, UWG_BOTTOMLEFT2, GF_NONE,    function_cast<UpdateGeometryFunc>(ReadyAmmoIconWidget_UpdateGeometry), function_cast<DrawFunc>(ReadyAmmoIconWidget_Drawer), &hud->readyammoiconId },
900         { GUI_READYAMMO, ALIGN_BOTTOMLEFT,  UWG_BOTTOMLEFT2,    GF_FONTB,   function_cast<UpdateGeometryFunc>(ReadyAmmo_UpdateGeometry), function_cast<DrawFunc>(ReadyAmmo_Drawer), &hud->readyammoId },
901         { GUI_FRAGS,    ALIGN_BOTTOMLEFT,   UWG_BOTTOMLEFT,     GF_FONTA,   function_cast<UpdateGeometryFunc>(FragsWidget_UpdateGeometry), function_cast<DrawFunc>(FragsWidget_Draw), &hud->fragsId },
902         { GUI_ARMOR,    ALIGN_BOTTOMRIGHT,  UWG_BOTTOMRIGHT,    GF_FONTB,   function_cast<UpdateGeometryFunc>(Armor_UpdateGeometry), function_cast<DrawFunc>(ArmorWidget_Draw), &hud->armorId },
903         { GUI_ARMORICON, ALIGN_BOTTOMRIGHT, UWG_BOTTOMRIGHT,    GF_NONE,    nullptr, nullptr, &hud->armoriconId },
904         { GUI_KEYS,     ALIGN_BOTTOMRIGHT,  UWG_BOTTOMRIGHT,    GF_NONE,    nullptr, nullptr, &hud->keysId },
905         { GUI_FACE,     ALIGN_BOTTOM,       UWG_BOTTOMCENTER,   GF_NONE,    function_cast<UpdateGeometryFunc>(Face_UpdateGeometry), function_cast<DrawFunc>(Face_Drawer), &hud->faceId },
906         { GUI_SECRETS,  ALIGN_TOPLEFT,      UWG_COUNTERS,       GF_FONTA,   nullptr, nullptr, &hud->secretsId },
907         { GUI_ITEMS,    ALIGN_TOPLEFT,      UWG_COUNTERS,       GF_FONTA,   nullptr, nullptr, &hud->itemsId },
908         { GUI_KILLS,    ALIGN_TOPLEFT,      UWG_COUNTERS,       GF_FONTA,   nullptr, nullptr, &hud->killsId }
909     };
910 
911     if(localPlayer < 0 || localPlayer >= MAXPLAYERS)
912     {
913         Con_Error("ST_BuildWidgets: Invalid localPlayer #%i.", localPlayer);
914         exit(1); // Unreachable.
915     }
916 
917     for(uiwidgetgroupdef_t const &def : widgetGroupDefs)
918     {
919         HudWidget *grp = makeGroupWidget(def.groupFlags, localPlayer, def.alignFlags, def.order, def.padding);
920         GUI_AddWidget(grp);
921         hud->groupIds[def.group] = grp->id();
922     }
923 
924     GUI_FindWidgetById(hud->groupIds[UWG_BOTTOMLEFT]).as<GroupWidget>()
925             .addChild(&GUI_FindWidgetById(hud->groupIds[UWG_BOTTOMLEFT2]));
926 
927     for(uiwidgetdef_t const &def : widgetDefs)
928     {
929         HudWidget *wi = nullptr;
930         switch(def.type)
931         {
932         case GUI_BOX:           wi = new HudWidget(def.updateGeometry, def.drawer, localPlayer); break;
933         case GUI_HEALTH:        wi = new guidata_health_t(def.updateGeometry, def.drawer, localPlayer); break;
934         case GUI_ARMOR:         wi = new guidata_armor_t(def.updateGeometry, def.drawer, localPlayer); break;
935         case GUI_KEYS:          wi = new guidata_keys_t(localPlayer); break;
936         case GUI_READYAMMO:     wi = new guidata_readyammo_t(def.updateGeometry, def.drawer, localPlayer); break;
937         case GUI_FRAGS:         wi = new guidata_frags_t(def.updateGeometry, def.drawer, localPlayer); break;
938         case GUI_AMMO:          wi = new guidata_ammo_t(localPlayer); break;
939         case GUI_MAXAMMO:       wi = new guidata_maxammo_t(localPlayer); break;
940         case GUI_WEAPONSLOT:    wi = new guidata_weaponslot_t(localPlayer); break;
941         case GUI_FACE:          wi = new guidata_face_t(def.updateGeometry, def.drawer, localPlayer); break;
942         case GUI_HEALTHICON:    wi = new guidata_healthicon_t(localPlayer, SPR_STIM); break;
943         case GUI_ARMORICON:     wi = new guidata_armoricon_t(localPlayer, SPR_ARM1, SPR_ARM2); break;
944         case GUI_READYAMMOICON: wi = new guidata_readyammoicon_t(def.updateGeometry, def.drawer, localPlayer); break;
945         case GUI_KEYSLOT:       wi = new guidata_keyslot_t(localPlayer); break;
946         case GUI_SECRETS:       wi = new guidata_secrets_t(localPlayer); break;
947         case GUI_ITEMS:         wi = new guidata_items_t(localPlayer); break;
948         case GUI_KILLS:         wi = new guidata_kills_t(localPlayer); break;
949 
950         default: DENG2_ASSERT(!"Unknown widget type"); break;
951         }
952 
953         wi->setAlignment(def.alignFlags)
954            .setFont(FID(def.fontIdx));
955         GUI_AddWidget(wi);
956         GUI_FindWidgetById(hud->groupIds[def.group]).as<GroupWidget>()
957                 .addChild(wi);
958 
959         if(def.id) *def.id = wi->id();
960     }
961 
962     GUI_FindWidgetById(hud->groupIds[UWG_BOTTOM]).as<GroupWidget>()
963             .addChild(&GUI_FindWidgetById(hud->groupIds[UWG_BOTTOMLEFT]));
964     GUI_FindWidgetById(hud->groupIds[UWG_BOTTOM]).as<GroupWidget>()
965             .addChild(&GUI_FindWidgetById(hud->groupIds[UWG_BOTTOMCENTER]));
966     GUI_FindWidgetById(hud->groupIds[UWG_BOTTOM]).as<GroupWidget>()
967             .addChild(&GUI_FindWidgetById(hud->groupIds[UWG_BOTTOMRIGHT]));
968 
969     auto *log = new PlayerLogWidget(localPlayer);
970     log->setFont(FID(GF_FONTA));
971     GUI_AddWidget(log);
972     hud->logId = log->id();
973     GUI_FindWidgetById(hud->groupIds[UWG_TOPCENTER]).as<GroupWidget>()
974             .addChild(log);
975 
976     auto *chat = new ChatWidget(localPlayer);
977     chat->setFont(FID(GF_FONTA));
978     GUI_AddWidget(chat);
979     hud->chatId = chat->id();
980     GUI_FindWidgetById(hud->groupIds[UWG_TOPCENTER]).as<GroupWidget>()
981             .addChild(chat);
982 
983     auto *automap = new AutomapWidget(localPlayer);
984     automap->setFont(FID(GF_FONTA));
985     automap->setCameraFollowPlayer(localPlayer);
986     /// Set initial geometry size.
987     /// @todo Should not be necessary...
988     Rect_SetWidthHeight(&automap->geometry(), SCREENWIDTH, SCREENHEIGHT);
989     GUI_AddWidget(automap);
990     hud->automapId = automap->id();
991     GUI_FindWidgetById(hud->groupIds[UWG_AUTOMAP]).as<GroupWidget>()
992             .addChild(automap);
993 
994 #undef PADDING
995 }
996 
ST_Init()997 void ST_Init()
998 {
999     ST_InitAutomapStyle();
1000     for(int i = 0; i < MAXPLAYERS; ++i)
1001     {
1002         hudstate_t *hud = &hudStates[i];
1003         ST_BuildWidgets(i);
1004         hud->inited = true;
1005     }
1006     ST_loadData();
1007 }
1008 
ST_Shutdown()1009 void ST_Shutdown()
1010 {
1011     for(int i = 0; i < MAXPLAYERS; ++i)
1012     {
1013         hudstate_t *hud = &hudStates[i];
1014         hud->inited = false;
1015     }
1016 }
1017 
HU_WakeWidgets(int localPlayer)1018 void HU_WakeWidgets(int localPlayer)
1019 {
1020     if(localPlayer < 0)
1021     {
1022         for(uint i = 0; i < MAXPLAYERS; ++i)
1023         {
1024             HU_WakeWidgets(i);
1025         }
1026     }
1027     else if(localPlayer < MAXPLAYERS)
1028     {
1029         if(players[localPlayer].plr->inGame)
1030         {
1031             ST_Start(localPlayer);
1032         }
1033     }
1034 
1035 }
1036 
ST_CloseAll(int localPlayer,dd_bool fast)1037 void ST_CloseAll(int localPlayer, dd_bool fast)
1038 {
1039     NetSv_DismissHUDs(localPlayer, true);
1040 
1041     ST_AutomapOpen(localPlayer, false, fast);
1042 }
1043 
1044 /// @note May be called prior to HUD init / outside game session.
ST_TryFindAutomapWidget(int localPlayer)1045 AutomapWidget *ST_TryFindAutomapWidget(int localPlayer)
1046 {
1047     if(localPlayer < 0 || localPlayer >= MAXPLAYERS) return nullptr;
1048     hudstate_t *hud = &hudStates[localPlayer];
1049     if(auto *wi = GUI_TryFindWidgetById(hud->automapId))
1050     {
1051         return maybeAs<AutomapWidget>(wi);
1052     }
1053     return nullptr;
1054 }
1055 
1056 /// @note May be called prior to HUD init / outside game session.
ST_TryFindChatWidget(int localPlayer)1057 ChatWidget *ST_TryFindChatWidget(int localPlayer)
1058 {
1059     if(localPlayer < 0 || localPlayer >= MAXPLAYERS) return nullptr;
1060     hudstate_t *hud = &hudStates[localPlayer];
1061     if(auto *wi = GUI_TryFindWidgetById(hud->chatId))
1062     {
1063         return maybeAs<ChatWidget>(wi);
1064     }
1065     return nullptr;
1066 }
1067 
1068 /// @note May be called prior to HUD init / outside game session.
ST_TryFindPlayerLogWidget(int localPlayer)1069 PlayerLogWidget *ST_TryFindPlayerLogWidget(int localPlayer)
1070 {
1071     if(localPlayer < 0 || localPlayer >= MAXPLAYERS) return nullptr;
1072     hudstate_t *hud = &hudStates[localPlayer];
1073     if(auto *wi = GUI_TryFindWidgetById(hud->logId))
1074     {
1075         return maybeAs<PlayerLogWidget>(wi);
1076     }
1077     return nullptr;
1078 }
1079 
ST_ChatIsActive(int localPlayer)1080 dd_bool ST_ChatIsActive(int localPlayer)
1081 {
1082     if(auto *chat = ST_TryFindChatWidget(localPlayer))
1083     {
1084         return chat->isActive();
1085     }
1086     return false;
1087 }
1088 
ST_LogPost(int localPlayer,byte flags,char const * msg)1089 void ST_LogPost(int localPlayer, byte flags, char const *msg)
1090 {
1091     if(auto *log = ST_TryFindPlayerLogWidget(localPlayer))
1092     {
1093         log->post(flags, msg);
1094     }
1095 }
1096 
ST_LogRefresh(int localPlayer)1097 void ST_LogRefresh(int localPlayer)
1098 {
1099     if(auto *log = ST_TryFindPlayerLogWidget(localPlayer))
1100     {
1101         log->refresh();
1102     }
1103 }
1104 
ST_LogEmpty(int localPlayer)1105 void ST_LogEmpty(int localPlayer)
1106 {
1107     if(auto *log = ST_TryFindPlayerLogWidget(localPlayer))
1108     {
1109         log->clear();
1110     }
1111 }
1112 
ST_LogUpdateAlignment()1113 void ST_LogUpdateAlignment()
1114 {
1115     for(int i = 0; i < MAXPLAYERS; ++i)
1116     {
1117         hudstate_t *hud = &hudStates[i];
1118         if(!hud->inited) continue;
1119 
1120         HudWidget &tcGroup = GUI_FindWidgetById(hud->groupIds[UWG_TOPCENTER]);
1121         int flags = tcGroup.alignment();
1122         flags &= ~(ALIGN_LEFT|ALIGN_RIGHT);
1123         if(cfg.common.msgAlign == 0)
1124             flags |= ALIGN_LEFT;
1125         else if(cfg.common.msgAlign == 2)
1126             flags |= ALIGN_RIGHT;
1127         tcGroup.setAlignment(flags);
1128     }
1129 }
1130 
ST_AutomapOpen(int localPlayer,dd_bool yes,dd_bool instantly)1131 void ST_AutomapOpen(int localPlayer, dd_bool yes, dd_bool instantly)
1132 {
1133     if(auto *automap = ST_TryFindAutomapWidget(localPlayer))
1134     {
1135         automap->open(CPP_BOOL(yes), CPP_BOOL(instantly));
1136     }
1137 }
1138 
ST_AutomapIsOpen(int localPlayer)1139 dd_bool ST_AutomapIsOpen(int localPlayer)
1140 {
1141     if(auto *automap = ST_TryFindAutomapWidget(localPlayer))
1142     {
1143         return automap->isOpen();
1144     }
1145     return false;
1146 }
1147 
ST_AutomapObscures2(int localPlayer,RectRaw const *)1148 dd_bool ST_AutomapObscures2(int localPlayer, RectRaw const * /*region*/)
1149 {
1150     AutomapWidget *automap = ST_TryFindAutomapWidget(localPlayer);
1151     if(!automap) return false;
1152 
1153     if(automap->isOpen())
1154     {
1155         if(cfg.common.automapOpacity * ST_AutomapOpacity(localPlayer) >= ST_AUTOMAP_OBSCURE_TOLERANCE)
1156         {
1157                 return true;
1158         }
1159     }
1160     return false;
1161 }
1162 
ST_AutomapObscures(int localPlayer,int x,int y,int width,int height)1163 dd_bool ST_AutomapObscures(int localPlayer, int x, int y, int width, int height)
1164 {
1165     RectRaw rect;
1166     rect.origin.x = x;
1167     rect.origin.y = y;
1168     rect.size.width  = width;
1169     rect.size.height = height;
1170     return ST_AutomapObscures2(localPlayer, &rect);
1171 }
1172 
ST_AutomapClearPoints(int localPlayer)1173 void ST_AutomapClearPoints(int localPlayer)
1174 {
1175     if(auto *automap = ST_TryFindAutomapWidget(localPlayer))
1176     {
1177         automap->clearAllPoints();
1178     }
1179 }
1180 
ST_AutomapAddPoint(int localPlayer,coord_t x,coord_t y,coord_t z)1181 int ST_AutomapAddPoint(int localPlayer, coord_t x, coord_t y, coord_t z)
1182 {
1183     if(auto *automap = ST_TryFindAutomapWidget(localPlayer))
1184     {
1185         return automap->addPoint(Vector3d(x, y, z));
1186     }
1187     return -1;
1188 }
1189 
ST_AutomapZoomMode(int localPlayer)1190 void ST_AutomapZoomMode(int localPlayer)
1191 {
1192     if(auto *automap = ST_TryFindAutomapWidget(localPlayer))
1193     {
1194         automap->setCameraZoomMode(!automap->cameraZoomMode());
1195     }
1196 }
1197 
ST_AutomapOpacity(int localPlayer)1198 float ST_AutomapOpacity(int localPlayer)
1199 {
1200     if(auto *automap = ST_TryFindAutomapWidget(localPlayer))
1201     {
1202         return automap->opacityEX();
1203     }
1204     return 0;
1205 }
1206 
ST_SetAutomapCameraRotation(int localPlayer,dd_bool yes)1207 void ST_SetAutomapCameraRotation(int localPlayer, dd_bool yes)
1208 {
1209     if(auto *autmap = ST_TryFindAutomapWidget(localPlayer))
1210     {
1211         autmap->setCameraRotationMode(CPP_BOOL(yes));
1212     }
1213 }
1214 
ST_AutomapFollowMode(int localPlayer)1215 void ST_AutomapFollowMode(int localPlayer)
1216 {
1217     if(auto *automap = ST_TryFindAutomapWidget(localPlayer))
1218     {
1219         automap->setCameraFollowMode(!automap->cameraFollowMode());
1220     }
1221 }
1222 
ST_CycleAutomapCheatLevel(int localPlayer)1223 void ST_CycleAutomapCheatLevel(int localPlayer)
1224 {
1225     if(localPlayer >= 0 && localPlayer < MAXPLAYERS)
1226     {
1227         hudstate_t *hud = &hudStates[localPlayer];
1228         ST_SetAutomapCheatLevel(localPlayer, (hud->automapCheatLevel + 1) % 3);
1229     }
1230 }
1231 
ST_SetAutomapCheatLevel(int localPlayer,int level)1232 void ST_SetAutomapCheatLevel(int localPlayer, int level)
1233 {
1234     if(auto *automap = ST_TryFindAutomapWidget(localPlayer))
1235     {
1236         setAutomapCheatLevel(*automap, level);
1237     }
1238 }
1239 
ST_RevealAutomap(int localPlayer,dd_bool on)1240 void ST_RevealAutomap(int localPlayer, dd_bool on)
1241 {
1242     if(auto *automap = ST_TryFindAutomapWidget(localPlayer))
1243     {
1244         automap->reveal(on);
1245     }
1246 }
1247 
ST_AutomapIsRevealed(int localPlayer)1248 dd_bool ST_AutomapIsRevealed(int localPlayer)
1249 {
1250     if(auto *automap = ST_TryFindAutomapWidget(localPlayer))
1251     {
1252         return automap->isRevealed();
1253     }
1254     return false;
1255 }
1256 
ST_AutomapCheatLevel(int localPlayer)1257 int ST_AutomapCheatLevel(int localPlayer)
1258 {
1259     if(localPlayer >= 0 && localPlayer < MAXPLAYERS)
1260     {
1261         return hudStates[localPlayer].automapCheatLevel;
1262     }
1263     return 0;
1264 }
1265 
1266 /// @note Called when the statusbar scale cvar changes.
updateViewWindow()1267 static void updateViewWindow()
1268 {
1269     R_ResizeViewWindow(RWF_FORCE);
1270     // Reveal the HUD so the user can see the change.
1271     for(int i = 0; i < MAXPLAYERS; ++i)
1272     {
1273         ST_HUDUnHide(i, HUE_FORCE);
1274     }
1275 }
1276 
1277 /**
1278  * Called when a cvar changes that affects the look/behavior of the HUD
1279  * in order to unhide it.
1280  */
unhideHUD()1281 static void unhideHUD()
1282 {
1283     for(int i = 0; i < MAXPLAYERS; ++i)
1284     {
1285         ST_HUDUnHide(i, HUE_FORCE);
1286     }
1287 }
1288 
1289 /**
1290  * @return  Parsed chat macro identifier or @c -1 if invalid.
1291  */
parseMacroId(String const & str)1292 static int parseMacroId(String const &str) // static
1293 {
1294     if(!str.isEmpty())
1295     {
1296         bool isNumber = false;
1297         int const id  = str.toInt(&isNumber);
1298         if(isNumber && id >= 0 && id <= 9)
1299         {
1300             return id;
1301         }
1302     }
1303     return -1;
1304 }
1305 
1306 /**
1307  * @return  Parsed chat destination number from or @c -1 if invalid.
1308  */
parseTeamNumber(String const & str)1309 static int parseTeamNumber(String const &str)
1310 {
1311     if(!str.isEmpty())
1312     {
1313         bool isNumber = false;
1314         int const num = str.toInt(&isNumber);
1315         if(isNumber && num >= 0 && num <= NUMTEAMS)
1316         {
1317             return num;
1318         }
1319     }
1320     return -1;
1321 }
1322 
D_CMD(ChatOpen)1323 D_CMD(ChatOpen)
1324 {
1325     DENG2_UNUSED(src);
1326 
1327     if(G_QuitInProgress()) return false;
1328 
1329     ChatWidget *chat = ST_TryFindChatWidget(CONSOLEPLAYER);
1330     if(!chat) return false;
1331 
1332     int destination = 0;
1333     if(argc == 2)
1334     {
1335         destination = parseTeamNumber(argv[1]);
1336         if(destination < 0)
1337         {
1338             LOG_SCR_ERROR("Invalid team number #%i (valid range: 0..%i)") << destination << NUMTEAMS;
1339             return false;
1340         }
1341     }
1342     chat->setDestination(destination);
1343     chat->activate();
1344     return true;
1345 }
1346 
D_CMD(ChatAction)1347 D_CMD(ChatAction)
1348 {
1349     DENG2_UNUSED2(src, argc);
1350 
1351     if(G_QuitInProgress()) return false;
1352 
1353     ChatWidget *chat = ST_TryFindChatWidget(CONSOLEPLAYER);
1354     if(!chat || !chat->isActive()) return false;
1355 
1356     auto const cmd = String(argv[0] + 4);
1357     if(!cmd.compareWithoutCase("complete")) // Send the message.
1358     {
1359         return chat->handleMenuCommand(MCMD_SELECT);
1360     }
1361     if(!cmd.compareWithoutCase("cancel")) // Close chat.
1362     {
1363         return chat->handleMenuCommand(MCMD_CLOSE);
1364     }
1365     if(!cmd.compareWithoutCase("delete"))
1366     {
1367         return chat->handleMenuCommand(MCMD_DELETE);
1368     }
1369     return true;
1370 }
1371 
D_CMD(ChatSendMacro)1372 D_CMD(ChatSendMacro)
1373 {
1374     DENG2_UNUSED(src);
1375 
1376     if(G_QuitInProgress()) return false;
1377 
1378     if(argc < 2 || argc > 3)
1379     {
1380         LOG_SCR_NOTE("Usage: %s (team) (macro number)") << argv[0];
1381         LOG_SCR_MSG("Send a chat macro to other player(s). "
1382                     "If (team) is omitted, the message will be sent to all players.");
1383         return true;
1384     }
1385 
1386     ChatWidget *chat = ST_TryFindChatWidget(CONSOLEPLAYER);
1387     if(!chat) return false;
1388 
1389     int destination = 0;
1390     if(argc == 3)
1391     {
1392         destination = parseTeamNumber(argv[1]);
1393         if(destination < 0)
1394         {
1395             LOG_SCR_ERROR("Invalid team number #%i (valid range: 0..%i)") << destination << NUMTEAMS;
1396             return false;
1397         }
1398     }
1399 
1400     int macroId = parseMacroId(argc == 3? argv[2] : argv[1]);
1401     if(macroId < 0)
1402     {
1403         LOG_SCR_ERROR("Invalid macro id");
1404         return false;
1405     }
1406 
1407     chat->activate();
1408     chat->setDestination(destination);
1409     chat->messageAppendMacro(macroId);
1410     chat->handleMenuCommand(MCMD_SELECT);
1411     chat->activate(false);
1412 
1413     return true;
1414 }
1415 
ST_Register()1416 void ST_Register()
1417 {
1418     C_VAR_FLOAT2("hud-color-r",                     &cfg.common.hudColor[0], 0, 0, 1, unhideHUD )
1419     C_VAR_FLOAT2("hud-color-g",                     &cfg.common.hudColor[1], 0, 0, 1, unhideHUD )
1420     C_VAR_FLOAT2("hud-color-b",                     &cfg.common.hudColor[2], 0, 0, 1, unhideHUD )
1421     C_VAR_FLOAT2("hud-color-a",                     &cfg.common.hudColor[3], 0, 0, 1, unhideHUD )
1422     C_VAR_FLOAT2("hud-icon-alpha",                  &cfg.common.hudIconAlpha, 0, 0, 1, unhideHUD )
1423     C_VAR_INT   ("hud-patch-replacement",           &cfg.common.hudPatchReplaceMode, 0, 0, 1 )
1424     C_VAR_FLOAT2("hud-scale",                       &cfg.common.hudScale, 0, 0.1f, 1, unhideHUD )
1425     C_VAR_FLOAT ("hud-timer",                       &cfg.common.hudTimer, 0, 0, 60 )
1426 
1427     // Displays
1428     C_VAR_BYTE2 ("hud-ammo",                        &cfg.hudShown[HUD_AMMO], 0, 0, 1, unhideHUD )
1429     C_VAR_BYTE2 ("hud-armor",                       &cfg.hudShown[HUD_ARMOR], 0, 0, 1, unhideHUD )
1430     C_VAR_BYTE2 ("hud-cheat-counter",               &cfg.common.hudShownCheatCounters, 0, 0, 63, unhideHUD )
1431     C_VAR_FLOAT2("hud-cheat-counter-scale",         &cfg.common.hudCheatCounterScale, 0, .1f, 1, unhideHUD )
1432     C_VAR_BYTE2 ("hud-cheat-counter-show-mapopen",  &cfg.common.hudCheatCounterShowWithAutomap, 0, 0, 1, unhideHUD )
1433     C_VAR_BYTE2 ("hud-face",                        &cfg.hudShown[HUD_FACE], 0, 0, 1, unhideHUD )
1434     C_VAR_BYTE  ("hud-face-ouchfix",                &cfg.fixOuchFace, 0, 0, 1 )
1435     C_VAR_BYTE2 ("hud-frags",                       &cfg.hudShown[HUD_FRAGS], 0, 0, 1, unhideHUD )
1436     C_VAR_BYTE2 ("hud-health",                      &cfg.hudShown[HUD_HEALTH], 0, 0, 1, unhideHUD )
1437     C_VAR_BYTE2 ("hud-keys",                        &cfg.hudShown[HUD_KEYS], 0, 0, 1, unhideHUD )
1438     C_VAR_BYTE2 ("hud-keys-combine",                &cfg.hudKeysCombine, 0, 0, 1, unhideHUD )
1439 
1440     C_VAR_FLOAT2("hud-status-alpha",                &cfg.common.statusbarOpacity, 0, 0, 1, unhideHUD )
1441     C_VAR_FLOAT2("hud-status-icon-a",               &cfg.common.statusbarCounterAlpha, 0, 0, 1, unhideHUD )
1442     C_VAR_FLOAT2("hud-status-size",                 &cfg.common.statusbarScale, 0, 0.1f, 1, updateViewWindow )
1443     C_VAR_BYTE2 ("hud-status-weaponslots-ownedfix", &cfg.fixStatusbarOwnedWeapons, 0, 0, 1, unhideHUD )
1444 
1445     // Events.
1446     C_VAR_BYTE  ("hud-unhide-damage",               &cfg.hudUnHide[HUE_ON_DAMAGE], 0, 0, 1 )
1447     C_VAR_BYTE  ("hud-unhide-pickup-ammo",          &cfg.hudUnHide[HUE_ON_PICKUP_AMMO], 0, 0, 1 )
1448     C_VAR_BYTE  ("hud-unhide-pickup-armor",         &cfg.hudUnHide[HUE_ON_PICKUP_ARMOR], 0, 0, 1 )
1449     C_VAR_BYTE  ("hud-unhide-pickup-health",        &cfg.hudUnHide[HUE_ON_PICKUP_HEALTH], 0, 0, 1 )
1450     C_VAR_BYTE  ("hud-unhide-pickup-key",           &cfg.hudUnHide[HUE_ON_PICKUP_KEY], 0, 0, 1 )
1451     C_VAR_BYTE  ("hud-unhide-pickup-powerup",       &cfg.hudUnHide[HUE_ON_PICKUP_POWER], 0, 0, 1 )
1452     C_VAR_BYTE  ("hud-unhide-pickup-weapon",        &cfg.hudUnHide[HUE_ON_PICKUP_WEAPON], 0, 0, 1 )
1453 
1454     C_CMD( "beginchat",       nullptr,  ChatOpen )
1455     C_CMD( "chatcancel",      "",       ChatAction )
1456     C_CMD( "chatcomplete",    "",       ChatAction )
1457     C_CMD( "chatdelete",      "",       ChatAction )
1458     C_CMD( "chatsendmacro",   nullptr,  ChatSendMacro )
1459 }
1460