1 /** @file intermission.cpp  Hexen specific intermission screens.
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2005-2014 Daniel Swanson <danij@dengine.net>
5  * @authors Copyright © 1999 Activision
6  *
7  * @par License
8  * GPL: http://www.gnu.org/licenses/gpl.html
9  *
10  * <small>This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by the
12  * Free Software Foundation; either version 2 of the License, or (at your
13  * option) any later version. This program is distributed in the hope that it
14  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
15  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
16  * Public License for more details. You should have received a copy of the GNU
17  * General Public License along with this program; if not, write to the Free
18  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19  * 02110-1301 USA</small>
20  */
21 
22 #include "jhexen.h"
23 #include "intermission.h"
24 
25 #include <cstdio>
26 #include "d_net.h"
27 #include "d_netcl.h"
28 #include "d_netsv.h"
29 #include "hu_stuff.h"
30 #include "hu_menu.h"
31 #include "g_common.h"
32 
33 #define TEXTSPEED               (3)
34 #define TEXTWAIT                (140)
35 
36 #define TALLY_EFFECT_TICKS      (20)
37 #define TALLY_FINAL_X_DELTA     (23 * FRACUNIT)
38 #define TALLY_FINAL_Y_DELTA     (13 * FRACUNIT)
39 #define TALLY_START_XPOS        (178 * FRACUNIT)
40 #define TALLY_STOP_XPOS         (90 * FRACUNIT)
41 #define TALLY_START_YPOS        (132 * FRACUNIT)
42 #define TALLY_STOP_YPOS         (83 * FRACUNIT)
43 #define TALLY_TOP_X             (85)
44 #define TALLY_TOP_Y             (9)
45 #define TALLY_LEFT_X            (7)
46 #define TALLY_LEFT_Y            (71)
47 #define TALLY_TOTALS_X          (291)
48 
49 #define MAX_INTRMSN_MESSAGE_SIZE (1024)
50 
51 using namespace de;
52 
53 enum gametype_t
54 {
55     SINGLE,
56     COOPERATIVE,
57     DEATHMATCH
58 };
59 
60 static void IN_WaitStop();
61 static void loadPics();
62 static void unloadPics();
63 static void CheckForSkip();
64 static void initStats();
65 static void drawDeathTally();
66 static void drawNumber(int val, int x, int y, int wrapThresh);
67 static void drawNumberBold(int val, int x, int y, int wrapThresh);
68 
69 dd_bool intermission;
70 int interState;
71 int overrideHubMsg; // Override the hub transition message when 1.
72 
73 // Used for timing of background animation.
74 static int bcnt;
75 
76 static dd_bool skipIntermission;
77 static int interTime = -1;
78 static gametype_t gameType;
79 static int cnt;
80 static int slaughterBoy; // In DM, the player with the most kills.
81 static int totalFrags[MAXPLAYERS];
82 
83 static int hubCount;
84 
85 static patchid_t dpTallyTop;
86 static patchid_t dpTallyLeft;
87 
WI_initVariables()88 void WI_initVariables(/*wbstartstruct_t *wbstartstruct */)
89 {
90 /*    wbs = wbstartstruct;
91 
92 #ifdef RANGECHECK
93     if(gameMode != commercial)
94     {
95         if(gameMode == retail)
96             RNGCHECK(wbs->epsd, 0, 3);
97         else
98             RNGCHECK(wbs->epsd, 0, 2);
99     }
100     else
101     {
102         RNGCHECK(wbs->last, 0, 8);
103         RNGCHECK(wbs->next, 0, 8);
104     }
105     RNGCHECK(wbs->pnum, 0, MAXPLAYERS);
106     RNGCHECK(wbs->pnum, 0, MAXPLAYERS);
107 #endif
108 
109     accelerateStage = 0;
110     cnt =*/ bcnt = 0; /*
111     firstRefresh = 1;
112     me = wbs->pNum;
113     myTeam = cfg.playerColor[wbs->pNum];
114     plrs = wbs->plyr;
115 
116     if(!wbs->maxKills)
117         wbs->maxKills = 1;
118     if(!wbs->maxItems)
119         wbs->maxItems = 1;
120     if(!wbs->maxSecret)
121         wbs->maxSecret = 1;
122 
123     if(gameMode != retail)
124         if(wbs->epsd > 2)
125             wbs->epsd -= 3;*/
126 
127     intermission     = true;
128     interState       = 0;
129     skipIntermission = false;
130     interTime        = 0;
131 }
132 
IN_Begin(wbstartstruct_t const &)133 void IN_Begin(wbstartstruct_t const & /*wbstartstruct*/)
134 {
135     DENG2_ASSERT(gfw_Rule(deathmatch));
136 
137     WI_initVariables();
138     loadPics();
139     initStats();
140 }
141 
IN_WaitStop()142 void IN_WaitStop()
143 {
144     if(!--cnt)
145     {
146         IN_End();
147         G_IntermissionDone();
148     }
149 }
150 
IN_End()151 void IN_End()
152 {
153     NetSv_Intermission(IMF_END, 0, 0);
154     unloadPics();
155     intermission = false;
156 }
157 
158 /**
159  * Initializes the stats for single player mode.
160  */
initStats()161 static void initStats()
162 {
163     gameType = DEATHMATCH;
164     slaughterBoy = 0;
165 
166     int slaughterFrags = -9999;
167     int posNum         = 0;
168     int playerCount    = 0;
169     int slaughterCount = 0;
170 
171     for(int i = 0; i < MAXPLAYERS; ++i)
172     {
173         totalFrags[i] = 0;
174         if(players[i].plr->inGame)
175         {
176             playerCount++;
177             for(int k = 0; k < MAXPLAYERS; ++k)
178             {
179                 if(players[i].plr->inGame)
180                 {
181                     totalFrags[i] += players[i].frags[k];
182                 }
183             }
184             posNum++;
185         }
186 
187         if(totalFrags[i] > slaughterFrags)
188         {
189             slaughterBoy = 1 << i;
190             slaughterFrags = totalFrags[i];
191             slaughterCount = 1;
192         }
193         else if(totalFrags[i] == slaughterFrags)
194         {
195             slaughterBoy |= 1 << i;
196             slaughterCount++;
197         }
198     }
199 
200     if(playerCount == slaughterCount)
201     {
202         // Don't do the slaughter stuff if everyone is equal.
203         slaughterBoy = 0;
204     }
205 }
206 
loadPics()207 static void loadPics()
208 {
209     if(gameType != SINGLE)
210     {
211         dpTallyTop  = R_DeclarePatch("TALLYTOP");
212         dpTallyLeft = R_DeclarePatch("TALLYLFT");
213     }
214 }
215 
unloadPics()216 static void unloadPics()
217 {
218     // Nothing to do.
219 }
220 
IN_Ticker()221 void IN_Ticker()
222 {
223     if(!intermission) return;
224 
225     if(interState)
226     {
227         IN_WaitStop();
228         return;
229     }
230 
231     skipIntermission = false;
232     CheckForSkip();
233 
234     // Counter for general background animation.
235     bcnt++;
236 
237     interTime++;
238     if(skipIntermission || (gameType == SINGLE && !hubCount))
239     {
240         interState = 1;
241         NetSv_Intermission(IMF_STATE, interState, 0);
242         cnt = 10;
243         skipIntermission = false;
244     }
245 }
246 
CheckForSkip()247 static void CheckForSkip()
248 {
249     static bool triedToSkip;
250 
251     for(int i = 0; i < MAXPLAYERS; ++i)
252     {
253         player_t *player = &players[i];
254 
255         if(player->plr->inGame)
256         {
257             if(player->brain.attack)
258             {
259                 if(!player->attackDown)
260                 {
261                     if(IS_CLIENT)
262                     {
263                         NetCl_PlayerActionRequest(player, GPA_FIRE, 0);
264                     }
265                     else
266                     {
267                         IN_SkipToNext();
268                     }
269                 }
270                 player->attackDown = true;
271             }
272             else
273             {
274                 player->attackDown = false;
275             }
276 
277             if(player->brain.use)
278             {
279                 if(!player->useDown)
280                 {
281                     if(IS_CLIENT)
282                     {
283                         NetCl_PlayerActionRequest(player, GPA_USE, 0);
284                     }
285                     else
286                     {
287                         IN_SkipToNext();
288                     }
289                 }
290                 player->useDown = true;
291             }
292             else
293             {
294                 player->useDown = false;
295             }
296         }
297     }
298 
299     if(gfw_Rule(deathmatch) && interTime < 140)
300     {
301         // Wait for 4 seconds before allowing a skip.
302         if(skipIntermission == 1)
303         {
304             triedToSkip = true;
305             skipIntermission = 0;
306         }
307     }
308     else
309     {
310         if(triedToSkip)
311         {
312             skipIntermission = 1;
313             triedToSkip = false;
314         }
315     }
316 }
317 
IN_Drawer()318 void IN_Drawer()
319 {
320     if(!intermission || interState)
321         return;
322 
323     dgl_borderedprojectionstate_t bp;
324     GL_ConfigureBorderedProjection(&bp, BPF_OVERDRAW_MASK|BPF_OVERDRAW_CLIP, SCREENWIDTH, SCREENHEIGHT,
325                                    Get(DD_WINDOW_WIDTH), Get(DD_WINDOW_HEIGHT), scalemode_t(cfg.common.inludeScaleMode));
326     GL_BeginBorderedProjection(&bp);
327 
328     lumpnum_t lumpNum = CentralLumpIndex().findLast("INTERPIC.lmp");
329     if(lumpNum >= 0)
330     {
331         DGL_Color4f(1, 1, 1, 1);
332         DGL_SetRawImage(lumpNum, DGL_CLAMP_TO_EDGE, DGL_CLAMP_TO_EDGE);
333         DGL_Enable(DGL_TEXTURE_2D);
334         DGL_DrawRectf2(0, 0, SCREENWIDTH, SCREENHEIGHT);
335         DGL_Disable(DGL_TEXTURE_2D);
336     }
337 
338     if(gameType != SINGLE)
339     {
340         drawDeathTally();
341     }
342 
343     GL_EndBorderedProjection(&bp);
344 }
345 
drawDeathTally()346 static void drawDeathTally()
347 {
348     static dd_bool showTotals;
349 
350     DGL_Enable(DGL_TEXTURE_2D);
351 
352     DGL_Color4f(1, 1, 1, 1);
353     GL_DrawPatch(dpTallyTop,  Vector2i(TALLY_TOP_X, TALLY_TOP_Y));
354     GL_DrawPatch(dpTallyLeft, Vector2i(TALLY_LEFT_X, TALLY_LEFT_Y));
355 
356     fixed_t xPos, yPos, xDelta, yDelta, xStart, scale;
357     if(interTime < TALLY_EFFECT_TICKS)
358     {
359         showTotals = false;
360         scale = (interTime * FRACUNIT) / TALLY_EFFECT_TICKS;
361         xDelta = FixedMul(scale, TALLY_FINAL_X_DELTA);
362         yDelta = FixedMul(scale, TALLY_FINAL_Y_DELTA);
363         xStart = TALLY_START_XPOS - FixedMul(scale, TALLY_START_XPOS - TALLY_STOP_XPOS);
364         yPos   = TALLY_START_YPOS - FixedMul(scale, TALLY_START_YPOS - TALLY_STOP_YPOS);
365     }
366     else
367     {
368         xDelta = TALLY_FINAL_X_DELTA;
369         yDelta = TALLY_FINAL_Y_DELTA;
370         xStart = TALLY_STOP_XPOS;
371         yPos   = TALLY_STOP_YPOS;
372     }
373 
374     if(interTime >= TALLY_EFFECT_TICKS && showTotals == false)
375     {
376         showTotals = true;
377         S_StartSound(SFX_PLATFORM_STOP, NULL);
378     }
379     int y = yPos >> FRACBITS;
380 
381     FR_SetFont(FID(GF_FONTA));
382     FR_LoadDefaultAttrib();
383 
384     for(int i = 0; i < MAXPLAYERS; ++i)
385     {
386         xPos = xStart;
387         for(int j = 0; j < MAXPLAYERS; ++j, xPos += xDelta)
388         {
389             int x = xPos >> FRACBITS;
390             dd_bool bold = (i == CONSOLEPLAYER || j == CONSOLEPLAYER);
391             if(players[i].plr->inGame && players[j].plr->inGame)
392             {
393                 if(bold)
394                 {
395                     drawNumberBold(players[i].frags[j], x, y, 100);
396                 }
397                 else
398                 {
399                     drawNumber(players[i].frags[j], x, y, 100);
400                 }
401             }
402             else
403             {
404                 if(bold)
405                 {
406                     FR_SetColorAndAlpha(1, 0.7f, 0.3f, 1);
407                     FR_DrawTextXY3("--", x, y, ALIGN_TOP, DTF_NO_EFFECTS);
408                 }
409                 else
410                 {
411                     FR_SetColorAndAlpha(1, 1, 1, 1);
412                     FR_DrawTextXY("--", x, y);
413                 }
414             }
415         }
416 
417         if(showTotals && players[i].plr->inGame &&
418            !((slaughterBoy & (1 << i)) && !(interTime & 16)))
419         {
420             drawNumber(totalFrags[i], TALLY_TOTALS_X, y, 1000);
421         }
422 
423         yPos += yDelta;
424         y = yPos >> FRACBITS;
425     }
426 
427     DGL_Disable(DGL_TEXTURE_2D);
428 }
429 
drawNumber(int val,int x,int y,int wrapThresh)430 static void drawNumber(int val, int x, int y, int wrapThresh)
431 {
432     char buf[8] = "XX";
433 
434     if(!(val < -9 && wrapThresh < 1000))
435     {
436         sprintf(buf, "%d", val >= wrapThresh ? val % wrapThresh : val);
437     }
438 
439     FR_SetColorAndAlpha(1, 1, 1, 1);
440     FR_DrawTextXY3(buf, x, y, ALIGN_TOP, DTF_NO_EFFECTS);
441 }
442 
drawNumberBold(int val,int x,int y,int wrapThresh)443 static void drawNumberBold(int val, int x, int y, int wrapThresh)
444 {
445     char buf[8] = "XX";
446 
447     if(!(val < -9 && wrapThresh < 1000))
448     {
449         sprintf(buf, "%d", val >= wrapThresh ? val % wrapThresh : val);
450     }
451 
452     FR_SetColorAndAlpha(1, 0.7f, 0.3f, 1);
453     FR_DrawTextXY3(buf, x, y, ALIGN_TOP, DTF_NO_EFFECTS);
454 }
455 
IN_SetState(int stateNum)456 void IN_SetState(int stateNum)
457 {
458     interState = stateNum;
459 }
460 
IN_SkipToNext()461 void IN_SkipToNext()
462 {
463     skipIntermission = 1;
464 }
465 
IN_ConsoleRegister()466 void IN_ConsoleRegister()
467 {
468     C_VAR_BYTE("inlude-stretch",           &cfg.common.inludeScaleMode, 0, SCALEMODE_FIRST, SCALEMODE_LAST);
469     C_VAR_INT ("inlude-patch-replacement", &cfg.common.inludePatchReplaceMode, 0, 0, 1);
470 }
471