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