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