1  /*
2 
3  Copyright(C) 2005-2014 Simon Howard
4 
5  This program is free software; you can redistribute it and/or
6  modify it under the terms of the GNU General Public License
7  as published by the Free Software Foundation; either version 2
8  of the License, or (at your option) any later version.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  GNU General Public License for more details.
14 
15  --
16 
17  Functions for presenting the information captured from the statistics
18  buffer to a file.
19 
20  */
21 
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "d_player.h"
27 #include "d_mode.h"
28 #include "m_argv.h"
29 
30 #include "statdump.h"
31 
32 /* Par times for E1M1-E1M9. */
33 static const int doom1_par_times[] =
34 {
35     30, 75, 120, 90, 165, 180, 180, 30, 165,
36 };
37 
38 /* Par times for MAP01-MAP09. */
39 static const int doom2_par_times[] =
40 {
41     30, 90, 120, 120, 90, 150, 120, 120, 270,
42 };
43 
44 /* Player colors. */
45 static const char *player_colors[] =
46 {
47     "Green", "Indigo", "Brown", "Red"
48 };
49 
50 // Array of end-of-level statistics that have been captured.
51 
52 #define MAX_CAPTURES 32
53 static wbstartstruct_t captured_stats[MAX_CAPTURES];
54 static int num_captured_stats = 0;
55 
56 static GameMission_t discovered_gamemission = none;
57 
58 /* Try to work out whether this is a Doom 1 or Doom 2 game, by looking
59  * at the episode and map, and the par times.  This is used to decide
60  * how to format the level name.  Unfortunately, in some cases it is
61  * impossible to determine whether this is Doom 1 or Doom 2. */
62 
DiscoverGamemode(const wbstartstruct_t * stats,int num_stats)63 static void DiscoverGamemode(const wbstartstruct_t *stats, int num_stats)
64 {
65     int partime;
66     int level;
67     int i;
68 
69     if (discovered_gamemission != none)
70     {
71         return;
72     }
73 
74     for (i=0; i<num_stats; ++i)
75     {
76         level = stats[i].last;
77 
78         /* If episode 2, 3 or 4, this is Doom 1. */
79 
80         if (stats[i].epsd > 0)
81         {
82             discovered_gamemission = doom;
83             return;
84         }
85 
86         /* This is episode 1.  If this is level 10 or higher,
87            it must be Doom 2. */
88 
89         if (level >= 9)
90         {
91             discovered_gamemission = doom2;
92             return;
93         }
94 
95         /* Try to work out if this is Doom 1 or Doom 2 by looking
96            at the par time. */
97 
98         partime = stats[i].partime;
99 
100         if (partime == doom1_par_times[level] * TICRATE
101          && partime != doom2_par_times[level] * TICRATE)
102         {
103             discovered_gamemission = doom;
104             return;
105         }
106 
107         if (partime != doom1_par_times[level] * TICRATE
108          && partime == doom2_par_times[level] * TICRATE)
109         {
110             discovered_gamemission = doom2;
111             return;
112         }
113     }
114 }
115 
116 /* Returns the number of players active in the given stats buffer. */
117 
GetNumPlayers(const wbstartstruct_t * stats)118 static int GetNumPlayers(const wbstartstruct_t *stats)
119 {
120     int i;
121     int num_players = 0;
122 
123     for (i=0; i<MAXPLAYERS; ++i)
124     {
125         if (stats->plyr[i].in)
126         {
127             ++num_players;
128         }
129     }
130 
131     return num_players;
132 }
133 
PrintBanner(FILE * stream)134 static void PrintBanner(FILE *stream)
135 {
136     fprintf(stream, "===========================================\n");
137 }
138 
PrintPercentage(FILE * stream,int amount,int total)139 static void PrintPercentage(FILE *stream, int amount, int total)
140 {
141     if (total == 0)
142     {
143         fprintf(stream, "0");
144     }
145     else
146     {
147         fprintf(stream, "%i / %i", amount, total);
148 
149         // statdump.exe is a 16-bit program, so very occasionally an
150         // integer overflow can occur when doing this calculation with
151         // a large value. Therefore, cast to short to give the same
152         // output.
153 
154         fprintf(stream, " (%i%%)", (short) (amount * 100) / total);
155     }
156 }
157 
158 /* Display statistics for a single player. */
159 
PrintPlayerStats(FILE * stream,const wbstartstruct_t * stats,int player_num)160 static void PrintPlayerStats(FILE *stream, const wbstartstruct_t *stats,
161         int player_num)
162 {
163     const wbplayerstruct_t *player = &stats->plyr[player_num];
164 
165     fprintf(stream, "Player %i (%s):\n", player_num + 1,
166             player_colors[player_num]);
167 
168     /* Kills percentage */
169 
170     fprintf(stream, "\tKills: ");
171     PrintPercentage(stream, player->skills, stats->maxkills);
172     fprintf(stream, "\n");
173 
174     /* Items percentage */
175 
176     fprintf(stream, "\tItems: ");
177     PrintPercentage(stream, player->sitems, stats->maxitems);
178     fprintf(stream, "\n");
179 
180     /* Secrets percentage */
181 
182     fprintf(stream, "\tSecrets: ");
183     PrintPercentage(stream, player->ssecret, stats->maxsecret);
184     fprintf(stream, "\n");
185 }
186 
187 /* Frags table for multiplayer games. */
188 
PrintFragsTable(FILE * stream,const wbstartstruct_t * stats)189 static void PrintFragsTable(FILE *stream, const wbstartstruct_t *stats)
190 {
191     int x, y;
192 
193     fprintf(stream, "Frags:\n");
194 
195     /* Print header */
196 
197     fprintf(stream, "\t\t");
198 
199     for (x=0; x<MAXPLAYERS; ++x)
200     {
201 
202         if (!stats->plyr[x].in)
203         {
204             continue;
205         }
206 
207         fprintf(stream, "%s\t", player_colors[x]);
208     }
209 
210     fprintf(stream, "\n");
211 
212     fprintf(stream, "\t\t-------------------------------- VICTIMS\n");
213 
214     /* Print table */
215 
216     for (y=0; y<MAXPLAYERS; ++y)
217     {
218         if (!stats->plyr[y].in)
219         {
220             continue;
221         }
222 
223         fprintf(stream, "\t%s\t|", player_colors[y]);
224 
225         for (x=0; x<MAXPLAYERS; ++x)
226         {
227             if (!stats->plyr[x].in)
228             {
229                 continue;
230             }
231 
232             fprintf(stream, "%i\t", stats->plyr[y].frags[x]);
233         }
234 
235         fprintf(stream, "\n");
236     }
237 
238     fprintf(stream, "\t\t|\n");
239     fprintf(stream, "\t     KILLERS\n");
240 }
241 
242 /* Displays the level name: MAPxy or ExMy, depending on game mode. */
243 
PrintLevelName(FILE * stream,int episode,int level)244 static void PrintLevelName(FILE *stream, int episode, int level)
245 {
246     PrintBanner(stream);
247 
248     switch (discovered_gamemission)
249     {
250 
251         case doom:
252             fprintf(stream, "E%iM%i\n", episode + 1, level + 1);
253             break;
254         case doom2:
255             fprintf(stream, "MAP%02i\n", level + 1);
256             break;
257         default:
258         case none:
259             fprintf(stream, "E%iM%i / MAP%02i\n",
260                     episode + 1, level + 1, level + 1);
261             break;
262     }
263 
264     PrintBanner(stream);
265 }
266 
267 /* Print details of a statistics buffer to the given file. */
268 
PrintStats(FILE * stream,const wbstartstruct_t * stats)269 static void PrintStats(FILE *stream, const wbstartstruct_t *stats)
270 {
271     short leveltime, partime;
272     int i;
273 
274     PrintLevelName(stream, stats->epsd, stats->last);
275     fprintf(stream, "\n");
276 
277     leveltime = stats->plyr[0].stime / TICRATE;
278     partime = stats->partime / TICRATE;
279     fprintf(stream, "Time: %i:%02i", leveltime / 60, leveltime % 60);
280     fprintf(stream, " (par: %i:%02i)\n", partime / 60, partime % 60);
281     fprintf(stream, "\n");
282 
283     for (i=0; i<MAXPLAYERS; ++i)
284     {
285         if (stats->plyr[i].in)
286         {
287             PrintPlayerStats(stream, stats, i);
288         }
289     }
290 
291     if (GetNumPlayers(stats) >= 2)
292     {
293         PrintFragsTable(stream, stats);
294     }
295 
296     fprintf(stream, "\n");
297 }
298 
StatCopy(const wbstartstruct_t * stats)299 void StatCopy(const wbstartstruct_t *stats)
300 {
301     if (M_ParmExists("-statdump") && num_captured_stats < MAX_CAPTURES)
302     {
303         memcpy(&captured_stats[num_captured_stats], stats,
304                sizeof(wbstartstruct_t));
305         ++num_captured_stats;
306     }
307 }
308 
StatDump(void)309 void StatDump(void)
310 {
311     FILE *dumpfile;
312     int i;
313 
314     //!
315     // @category compat
316     // @arg <filename>
317     //
318     // Dump statistics information to the specified file on the levels
319     // that were played. The output from this option matches the output
320     // from statdump.exe (see ctrlapi.zip in the /idgames archive).
321     //
322 
323     i = M_CheckParmWithArgs("-statdump", 1);
324 
325     if (i > 0)
326     {
327         printf("Statistics captured for %i level(s)\n", num_captured_stats);
328 
329         // We actually know what the real gamemission is, but this has
330         // to match the output from statdump.exe.
331 
332         DiscoverGamemode(captured_stats, num_captured_stats);
333 
334         // Allow "-" as output file, for stdout.
335 
336         if (strcmp(myargv[i + 1], "-") != 0)
337         {
338             dumpfile = fopen(myargv[i + 1], "w");
339         }
340         else
341         {
342             dumpfile = stdout;
343         }
344 
345         for (i = 0; i < num_captured_stats; ++i)
346         {
347             PrintStats(dumpfile, &captured_stats[i]);
348         }
349 
350         if (dumpfile != stdout)
351         {
352             fclose(dumpfile);
353         }
354     }
355 }
356 
357