1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell
5  * or otherwise commercially exploit the source or things you created based on the
6  * source.
7  *
8 */
9 
10 
11 
12 
13 #include "debugconsole/console.h"
14 #include "gamesequence/gamesequence.h"
15 #include "gamesnd/gamesnd.h"
16 #include "globalincs/alphacolors.h"
17 #include "io/key.h"
18 #include "localization/localize.h"
19 #include "menuui/snazzyui.h"
20 #include "parse/parselo.h"
21 #include "playerman/player.h"
22 #include "popup/popup.h"
23 #include "stats/medals.h"
24 #include "ui/ui.h"
25 
26 #ifndef NDEBUG
27 #include "cmdline/cmdline.h"
28 #endif
29 
30 int Num_medals = 0;
31 
32 // define for the medal information
33 SCP_vector<medal_stuff> Medals;
34 
35 // coords for indiv medal bitmaps
36 static int Default_medal_coords[GR_NUM_RESOLUTIONS][NUM_MEDALS_FS2][2] = {
37 	{				// GR_640
38 		{ 89, 47 },			// eps. peg. lib
39 		{ 486, 47 },		// imp. order o' vasuda
40 		{ 129, 130 },		// dist flying cross
41 		{ 208, 132 },		// soc service
42 		{ 361, 131 },		// dist intel cross
43 		{ 439, 130 },		// order of galatea
44 		{ 64, 234 },		// meritorious unit comm.
45 		{ 153, 234 },		// medal of valor
46 		{ 239, 241 },		// gtva leg of honor
47 		{ 326, 240 },		// allied defense citation
48 		{ 411, 234 },		// neb campaign victory
49 		{ 494, 234 },		// ntf campaign victory
50 		{ 189, 80 },		// rank
51 		{ 283, 91 },		// wings
52 		{ 372, 76 },		// bronze kills badge
53 		{ 403, 76 },		// silver kills badge
54 		{ 435, 76 },		// gold kills badge
55 		{ 300, 152 },		// SOC unit crest
56 	},
57 	{				// GR_1024
58 		{ 143, 75 },		// eps. peg. lib
59 		{ 777, 75 },		// imp. order o' vasuda
60 		{ 206, 208 },		// dist flying cross
61 		{ 333, 212 },		// soc service
62 		{ 578, 210 },		// dist intel cross
63 		{ 703, 208 },		// order of galatea
64 		{ 103, 374 },		// meritorious unit comm.
65 		{ 245, 374 },		// medal of valor
66 		{ 383, 386 },		// gtva leg of honor
67 		{ 522, 384 },		// allied defense citation
68 		{ 658, 374 },		// neb campaign victory
69 		{ 790, 374 },		// ntf campaign victory
70 		{ 302, 128 },		// rank
71 		{ 453, 146 },		// wings
72 		{ 595, 121 },		// bronze kills badge
73 		{ 646, 121 },		// silver kills badge
74 		{ 696, 121 },		// gold kills badge
75 		{ 480, 244 },		// SOC unit crest
76 	}
77 };
78 
79 // debriefing bitmaps
80 static const char *Default_debriefing_bitmaps[NUM_MEDALS_FS2] =
81 {
82 	"DebriefMedal00",
83 	"DebriefMedal01",
84 	"DebriefMedal02",
85 	"DebriefMedal03",
86 	"DebriefMedal04",
87 	"DebriefMedal05",
88 	"DebriefMedal06",
89 	"DebriefMedal07",
90 	"DebriefMedal08",
91 	"DebriefMedal09",
92 	"DebriefMedal10",
93 	"DebriefMedal11",
94 	"DebriefRank##",
95 	"DebriefWings##",
96 	"DebriefBadge01",
97 	"DebriefBadge02",
98 	"DebriefBadge03",
99 	"DebriefCrest"
100 };
101 
102 // argh
103 typedef struct coord2dw {
104 	int x,y,w;
105 } coord2dw;
106 
107 // coords for the medal title
108 static int Default_medals_label_coords[GR_NUM_RESOLUTIONS][3] = {
109 	{ 241, 458, 300 },			// GR_640 x, y, w
110 	{ 386, 734, 480 }		// GR_1024 x, y, w
111 };
112 static coord2dw Medals_label_coords[GR_NUM_RESOLUTIONS];
113 
114 // coords for the player callsign
115 static int Default_medals_callsign_coords[GR_NUM_RESOLUTIONS][2] = {
116 	{ -1, 54 },		// we'll use -1 as a convention to center it
117 	{ -1, 89 }
118 };
119 static coord2d Medals_callsign_coords[GR_NUM_RESOLUTIONS];
120 
121 #define MEDALS_NUM_BUTTONS	1
122 #define MEDALS_EXIT			0
123 ui_button_info Medals_buttons[GR_NUM_RESOLUTIONS][MEDALS_NUM_BUTTONS] = {
124 	{ // GR_640
125 		ui_button_info("MEB_18",	574,	432,	-1,	-1,	18),
126 	},
127 	{ // GR_1024
128 		ui_button_info("2_MEB_18",	919,	691,	-1,	-1,	18),
129 	}
130 };
131 static int Exit_button_hotspot_override = -1;
132 
133 #define MEDALS_NUM_TEXT		1
134 UI_XSTR Medals_text[GR_NUM_RESOLUTIONS][MEDALS_NUM_TEXT] = {
135 	{	// GR_640
136 		{"Exit",		1466,		587,	416,	UI_XSTR_COLOR_PINK, -1,	&Medals_buttons[GR_640][MEDALS_EXIT].button },
137 	},
138 	{	// GR_1024
139 		{"Exit",		1466,		943,	673,	UI_XSTR_COLOR_PINK, -1,	&Medals_buttons[GR_1024][MEDALS_EXIT].button },
140 	},
141 };
142 
143 static const char* Default_medals_background_filename = "MedalsDisplayEmpty";
144 static char Medals_background_filename[NAME_LENGTH];
145 
146 static const char* Default_medals_mask_filename = "Medals-M";
147 static char Medals_mask_filename[NAME_LENGTH];
148 
149 scoring_struct *Player_score=NULL;
150 
151 int Medals_mode;
152 player *Medals_player;
153 
154 void init_medal_bitmaps();
155 void init_snazzy_regions();
156 void blit_medals();
157 
158 // -----------------------------------------------------------------------------
159 // Main medals screen state
160 //
161 
162 static bitmap *Medals_mask;
163 int Medals_mask_w, Medals_mask_h;
164 static int Medals_bitmap_mask;			// the mask for the medal case
165 static int Medals_bitmap;				// the medal case itself
166 static int Rank_bm;						// bitmap for the rank medal
167 
168 static UI_WINDOW Medals_window;
169 
170 typedef struct medal_display_info {
171 	int 		bitmap;						// image in the medal case
172 	coord2d		coords[GR_NUM_RESOLUTIONS];	// screen position (can now be defined by the table)
173 } medal_display_info;
174 
175 static SCP_vector<medal_display_info> Medal_display_info;
176 static MENU_REGION *Medal_regions = NULL;
177 
178 int Rank_medal_index = -1;
179 
180 
181 #define MEDAL_BITMAP_INIT (1<<0)
182 #define MASK_BITMAP_INIT  (1<<1)
183 int Init_flags;
184 
medal_stuff()185 medal_stuff::medal_stuff()
186 	: num_versions(1), version_starts_at_1(false), available_from_start(false), kills_needed(0)
187 {
188 	name[0] = '\0';
189 	bitmap[0] = '\0';
190 	debrief_bitmap[0] = '\0';
191 	voice_base[0] = '\0';
192 }
193 
get_display_name() const194 const char* medal_stuff::get_display_name() const {
195 	if (!alt_name.empty()) {
196 		return alt_name.c_str();
197 	} else {
198 		return name;
199 	}
200 }
201 
parse_medal_tbl()202 void parse_medal_tbl()
203 {
204 	int i;
205 
206 	try
207 	{
208 		read_file_text("medals.tbl", CF_TYPE_TABLES);
209 		reset_parse();
210 
211 		required_string("#Medals");
212 
213 		// special background information
214 		if (optional_string("+Background Bitmap:")) {
215 			stuff_string(Medals_background_filename, F_NAME, NAME_LENGTH);
216 		}
217 		else {
218 			strcpy_s(Medals_background_filename, Default_medals_background_filename);
219 		}
220 
221 		// special mask information
222 		if (optional_string("+Mask Bitmap:")) {
223 			stuff_string(Medals_mask_filename, F_NAME, NAME_LENGTH);
224 		}
225 		else {
226 			strcpy_s(Medals_mask_filename, Default_medals_mask_filename);
227 		}
228 
229 		// configurable hotspot for the exit button
230 		if (optional_string("+Exit Button Hotspot Index:")) {
231 			stuff_int(&Exit_button_hotspot_override);
232 		}
233 
234 		// special positioning for player callsign
235 		if (optional_string("+Callsign Position 640:")) {
236 			stuff_int(&Medals_callsign_coords[GR_640].x);
237 			stuff_int(&Medals_callsign_coords[GR_640].y);
238 		}
239 		else {
240 			Medals_callsign_coords[GR_640].x = Default_medals_callsign_coords[GR_640][0];
241 			Medals_callsign_coords[GR_640].y = Default_medals_callsign_coords[GR_640][1];
242 		}
243 		if (optional_string("+Callsign Position 1024:")) {
244 			stuff_int(&Medals_callsign_coords[GR_1024].x);
245 			stuff_int(&Medals_callsign_coords[GR_1024].y);
246 		}
247 		else {
248 			Medals_callsign_coords[GR_1024].x = Default_medals_callsign_coords[GR_1024][0];
249 			Medals_callsign_coords[GR_1024].y = Default_medals_callsign_coords[GR_1024][1];
250 		}
251 
252 		// special positioning for medal label
253 		if (optional_string("+Label Position 640:")) {
254 			stuff_int(&Medals_label_coords[GR_640].x);
255 			stuff_int(&Medals_label_coords[GR_640].y);
256 			stuff_int(&Medals_label_coords[GR_640].w);
257 		}
258 		else {
259 			Medals_label_coords[GR_640].x = Default_medals_label_coords[GR_640][0];
260 			Medals_label_coords[GR_640].y = Default_medals_label_coords[GR_640][1];
261 			Medals_label_coords[GR_640].w = Default_medals_label_coords[GR_640][2];
262 		}
263 		if (optional_string("+Label Position 1024:")) {
264 			stuff_int(&Medals_label_coords[GR_1024].x);
265 			stuff_int(&Medals_label_coords[GR_1024].y);
266 			stuff_int(&Medals_label_coords[GR_1024].w);
267 		}
268 		else {
269 			Medals_label_coords[GR_1024].x = Default_medals_label_coords[GR_1024][0];
270 			Medals_label_coords[GR_1024].y = Default_medals_label_coords[GR_1024][1];
271 			Medals_label_coords[GR_1024].w = Default_medals_label_coords[GR_1024][2];
272 		}
273 
274 		// parse in all the medal names
275 		Num_medals = 0;
276 		while (required_string_either("#End", "$Name:"))
277 		{
278 			medal_stuff temp_medal;
279 			medal_display_info temp_display;
280 
281 			required_string("$Name:");
282 			stuff_string(temp_medal.name, F_NAME, NAME_LENGTH);
283 
284 			// is this rank?  if so, save it
285 			if (!stricmp(temp_medal.name, "Rank"))
286 				Rank_medal_index = Num_medals;
287 
288 			if (optional_string("$Alt Name:")) {
289 				stuff_string(temp_medal.alt_name, F_NAME);
290 			}
291 
292 			required_string("$Bitmap:");
293 			stuff_string(temp_medal.bitmap, F_NAME, MAX_FILENAME_LEN);
294 
295 			if (optional_string("+Position 640:")) {
296 				stuff_int(&temp_display.coords[GR_640].x);
297 				stuff_int(&temp_display.coords[GR_640].y);
298 			}
299 			else if (Num_medals < NUM_MEDALS_FS2) {
300 				temp_display.coords[GR_640].x = Default_medal_coords[GR_640][Num_medals][0];
301 				temp_display.coords[GR_640].y = Default_medal_coords[GR_640][Num_medals][1];
302 			}
303 			else {
304 				Warning(LOCATION, "No default GR_640 position for medal '%s'!", temp_medal.name);
305 				temp_display.coords[GR_640].x = 0;
306 				temp_display.coords[GR_640].y = 0;
307 			}
308 			if (optional_string("+Position 1024:")) {
309 				stuff_int(&temp_display.coords[GR_1024].x);
310 				stuff_int(&temp_display.coords[GR_1024].y);
311 			}
312 			else if (Num_medals < NUM_MEDALS_FS2) {
313 				temp_display.coords[GR_1024].x = Default_medal_coords[GR_1024][Num_medals][0];
314 				temp_display.coords[GR_1024].y = Default_medal_coords[GR_1024][Num_medals][1];
315 			}
316 			else {
317 				Warning(LOCATION, "No default GR_1024 position for medal '%s'!", temp_medal.name);
318 				temp_display.coords[GR_1024].x = 0;
319 				temp_display.coords[GR_1024].y = 0;
320 			}
321 
322 			if (optional_string("+Debriefing Bitmap:")) {
323 				stuff_string(temp_medal.debrief_bitmap, F_NAME, MAX_FILENAME_LEN);
324 			}
325 			else if (Num_medals < NUM_MEDALS_FS2) {
326 				strcpy_s(temp_medal.debrief_bitmap, Default_debriefing_bitmaps[Num_medals]);
327 			}
328 			else {
329 				Warning(LOCATION, "No default debriefing bitmap for medal '%s'!", temp_medal.name);
330 				strcpy_s(temp_medal.debrief_bitmap, "");
331 			}
332 
333 			required_string("$Num mods:");
334 			stuff_int(&temp_medal.num_versions);
335 
336 			// this is dumb
337 			temp_medal.version_starts_at_1 = (Num_medals == Rank_medal_index);
338 			if (optional_string("+Version starts at 1:")) {
339 				stuff_boolean(&temp_medal.version_starts_at_1);
340 			}
341 
342 			if (optional_string("+Available From Start:")) {
343 				stuff_boolean(&temp_medal.available_from_start);
344 			}
345 
346 			// some medals are based on kill counts.  When string +Num Kills: is present, we know that
347 			// this medal is a badge and should be treated specially
348 			if (optional_string("+Num Kills:")) {
349 				char buf[MULTITEXT_LENGTH];
350 				int persona;
351 				stuff_int(&temp_medal.kills_needed);
352 
353 				if (optional_string("$Wavefile 1:"))
354 					stuff_string(temp_medal.voice_base, F_NAME, MAX_FILENAME_LEN);
355 
356 				if (optional_string("$Wavefile 2:"))
357 					stuff_string(temp_medal.voice_base, F_NAME, MAX_FILENAME_LEN);
358 
359 				if (optional_string("$Wavefile Base:"))
360 					stuff_string(temp_medal.voice_base, F_NAME, MAX_FILENAME_LEN);
361 
362 				while (check_for_string("$Promotion Text:")) {
363 					required_string("$Promotion Text:");
364 					stuff_string(buf, F_MULTITEXT, sizeof(buf));
365 					persona = -1;
366 					if (optional_string("+Persona:")) {
367 						stuff_int(&persona);
368 						if (persona < 0) {
369 							Warning(LOCATION, "Debriefing text for %s is assigned to an invalid persona: %i (must be 0 or greater).\n", temp_medal.name, persona);
370 							continue;
371 						}
372 					}
373 					temp_medal.promotion_text[persona] = SCP_string(buf);
374 				}
375 				if (temp_medal.promotion_text.find(-1) == temp_medal.promotion_text.end()) {
376 					Warning(LOCATION, "%s medal is missing default debriefing text.\n", temp_medal.name);
377 					temp_medal.promotion_text[-1] = "";
378 				}
379 			}
380 
381 			Medals.push_back(temp_medal);
382 			Medal_display_info.push_back(temp_display);
383 			Num_medals++;
384 		}
385 
386 		required_string("#End");
387 
388 		// be sure that we know where the rank is
389 		if (Rank_medal_index < 0)
390 		{
391 			Warning(LOCATION, "Could not find the 'Rank' medal!");
392 			Rank_medal_index = 0;
393 		}
394 
395 		// be sure that the badges kill numbers show up in order
396 		//WMC - I don't think this is needed anymore due to my changes to post-mission functions
397 		//but I'm keeping it here to be sure.
398 		int prev_badge_kills = 0;
399 		for (i = 0; i < Num_medals; i++)
400 		{
401 			if (Medals[i].kills_needed < prev_badge_kills && Medals[i].kills_needed != 0)
402 				Error(LOCATION, "Badges must appear sorted by lowest kill # first in medals.tbl\nFind Allender for most information.");
403 
404 			if (Medals[i].kills_needed > 0)
405 				prev_badge_kills = Medals[i].kills_needed;
406 		}
407 	}
408 	catch (const parse::ParseException& e)
409 	{
410 		mprintf(("TABLES: Unable to parse '%s'!  Error message = %s.\n", "medals.tbl", e.what()));
411 		return;
412 	}
413 }
414 
415 // replacement for -gimmemedals
416 DCF(medals, "Grant or revoke medals")
417 {
418 	int i;
419 	int idx;
420 
421 	if (dc_optional_string_either("help", "--help"))
422 	{
423 		dc_printf ("Usage: medals all | clear | promote | demote | [index]\n");
424 		dc_printf ("       [index] --  index of medal to grant\n");
425 		dc_printf ("       with no parameters, displays the available medals\n");
426 		return;
427 	}
428 
429 	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?"))
430 	{
431 		dc_printf("You have the following medals:\n");
432 
433 		for (i = 0; i < Num_medals; i++)
434 		{
435 			if (Player->stats.medal_counts[i] > 0)
436 				dc_printf("%d %s\n", Player->stats.medal_counts[i], Medals[i].name);
437 		}
438 		dc_printf("%s\n", Ranks[Player->stats.rank].name);
439 		return;
440 	}
441 
442 	if (dc_optional_string("all")) {
443 		for (i = 0; i < Num_medals; i++) {
444 			Player->stats.medal_counts[i]++;
445 		}
446 		dc_printf("Granted all medals\n");
447 		return;
448 
449 	} else if (dc_optional_string("clear")) {
450 		for (i = 0; i < Num_medals; i++) {
451 			Player->stats.medal_counts[i] = 0;
452 		}
453 		dc_printf("Cleared all medals\n");
454 		return;
455 
456 	} else if (dc_optional_string("promote")) {
457 		if (Player->stats.rank < MAX_FREESPACE2_RANK) {
458 			Player->stats.rank++;
459 		}
460 		dc_printf("Promoted to %s\n", Ranks[Player->stats.rank].name);
461 		return;
462 
463 	} else if (dc_optional_string("demote")) {
464 		if (Player->stats.rank > 0) {
465 			Player->stats.rank--;
466 		}
467 		dc_printf("Demoted to %s\n", Ranks[Player->stats.rank].name);
468 		return;
469 	}
470 
471 	if (dc_maybe_stuff_int(&idx)) {
472 		if (idx < 0 || idx >= Num_medals)
473 		{
474 			dc_printf("Medal index %d is out of range\n", idx);
475 			return;
476 		}
477 
478 		dc_printf("Granted %s\n", Medals[idx].name);
479 		Player->stats.medal_counts[idx]++;
480 		return;
481 	}
482 
483 	dc_printf("The following medals are available:\n");
484 	for (i = 0; i < Num_medals; i++) {
485 		dc_printf("%d: %s\n", i, Medals[i].name);
486 	}
487 }
488 
489 
medal_main_init(player * pl,int mode)490 void medal_main_init(player *pl, int mode)
491 {
492 	int idx;
493 	char bitmap_buf[NAME_LENGTH];
494 
495 	Assert(pl != NULL);
496 	Medals_player = pl;
497 	Player_score = &Medals_player->stats;
498 
499 #ifndef NDEBUG
500 	if (Cmdline_gimme_all_medals){
501 		for (idx=0; idx < Num_medals; idx++){
502 			Medals_player->stats.medal_counts[idx] = 1;
503 		}
504 	}
505 #endif
506 
507 	for (idx=0; idx < Num_medals; idx++) {
508 		if ((Medals[idx].available_from_start) && (Medals_player->stats.medal_counts[idx] < 1)) {
509 			Medals_player->stats.medal_counts[idx] = 1;
510 		}
511 	}
512 
513 	Medals_mode = mode;
514 	snazzy_menu_init();
515 	Medals_window.create( 0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled, 0 );
516 
517 	// maybe override which hotspot is used for the exit button
518 	if (Exit_button_hotspot_override >= 0) {
519 		for (idx = 0; idx < GR_NUM_RESOLUTIONS; idx++) {
520 			Medals_buttons[idx][MEDALS_EXIT].hotspot = Exit_button_hotspot_override;
521 		}
522 	}
523 
524 	// create the interface buttons
525 	for (idx=0; idx<MEDALS_NUM_BUTTONS; idx++) {
526 		// create the object
527 		Medals_buttons[gr_screen.res][idx].button.create(&Medals_window, "", Medals_buttons[gr_screen.res][idx].x, Medals_buttons[gr_screen.res][idx].y, 1, 1, 0, 1);
528 
529 		// set the sound to play when highlighted
530 		Medals_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
531 
532 		// set the ani for the button
533 		Medals_buttons[gr_screen.res][idx].button.set_bmaps(Medals_buttons[gr_screen.res][idx].filename);
534 
535 		// set the hotspot
536 		Medals_buttons[gr_screen.res][idx].button.link_hotspot(Medals_buttons[gr_screen.res][idx].hotspot);
537 	}
538 
539 	// add all xstrs
540 	for (idx=0; idx<MEDALS_NUM_TEXT; idx++) {
541 		Medals_window.add_XSTR(&Medals_text[gr_screen.res][idx]);
542 	}
543 
544 	Init_flags = 0;
545 
546 	strcpy_s(bitmap_buf, Resolution_prefixes[gr_screen.res]);
547 	strcat_s(bitmap_buf, Medals_background_filename);
548 
549 	Medals_bitmap = bm_load(bitmap_buf);
550 	if (Medals_bitmap < 0) {
551 		Warning(LOCATION, "Error loading medal background bitmap %s", bitmap_buf);
552 	} else {
553 		Init_flags |= MEDAL_BITMAP_INIT;
554 	}
555 
556 	Medals_mask_w = -1;
557 	Medals_mask_h = -1;
558 
559 	strcpy_s(bitmap_buf, Resolution_prefixes[gr_screen.res]);
560 	strcat_s(bitmap_buf, Medals_mask_filename);
561 
562 	Medals_bitmap_mask = bm_load(bitmap_buf);
563 	if (Medals_bitmap_mask < 0) {
564 		Warning(LOCATION, "Error loading medal mask file %s", bitmap_buf);
565 	} else {
566 		Init_flags |= MASK_BITMAP_INIT;
567 		Medals_mask = bm_lock(Medals_bitmap_mask, 8, BMP_AABITMAP | BMP_MASK_BITMAP);
568 		bm_get_info(Medals_bitmap_mask, &Medals_mask_w, &Medals_mask_h);
569 
570 		init_medal_bitmaps();
571 	}
572 
573 	init_snazzy_regions();
574 
575 	gr_set_color_fast(&Color_normal);
576 
577 	if (Medals_bitmap_mask >= 0)
578 		Medals_window.set_mask_bmap(bitmap_buf);
579 }
580 
blit_label(const char * label,int num)581 void blit_label(const char *label, int num)
582 {
583 	int x, y, sw;
584 	char text[256];
585 
586 	gr_set_color_fast(&Color_bright);
587 
588 	// translate medal names before displaying
589 	// can't translate in table cuz the names are used in comparisons
590 	// Medals now have alternate display names so this code can be disabled in the mod table
591 	if (Lcl_gr && !Disable_built_in_translations) {
592 		char translated_label[256];
593 		strcpy_s(translated_label, label);
594 		lcl_translate_medal_name_gr(translated_label);
595 
596 		// set correct string
597 		if ( num > 1 ) {
598 			sprintf_safe( text, NOX("%s (%d)"), translated_label, num );
599 		} else {
600 			sprintf_safe( text, "%s", translated_label );
601 		}
602 	} else if (Lcl_pl && !Disable_built_in_translations) {
603 		char translated_label[256];
604 		strcpy_s(translated_label, label);
605 		lcl_translate_medal_name_pl(translated_label);
606 
607 		// set correct string
608 		if ( num > 1 ) {
609 			sprintf_safe( text, NOX("%s (%d)"), translated_label, num );
610 		} else {
611 			sprintf_safe( text, "%s", translated_label );
612 		}
613 	} else {
614 		// set correct string
615 		if ( num > 1 ) {
616 			sprintf_safe( text, NOX("%s (%d)"), label, num );
617 		} else {
618 			sprintf_safe( text, "%s", label );
619 		}
620 	}
621 
622 	// find correct coords
623 	gr_get_string_size(&sw, NULL, text);
624 	x = Medals_label_coords[gr_screen.res].x + (Medals_label_coords[gr_screen.res].w - sw) / 2;
625 	y = Medals_label_coords[gr_screen.res].y;
626 
627 	// do it
628 	gr_string(x, y, text, GR_RESIZE_MENU);
629 }
630 
blit_callsign()631 void blit_callsign()
632 {
633 	int x, y;
634 
635 	gr_set_color_fast(&Color_normal);
636 
637 	// find correct coords
638 	x = Medals_callsign_coords[gr_screen.res].x;
639 	y = Medals_callsign_coords[gr_screen.res].y;
640 
641 	// nothing special, just do it.
642 	// Goober5000 - from previous code revisions, I assume 0x8000 means center it on-screen...
643 	// m!m - 0x8000 was removed so we need to calculate it ourself
644 	if (x < 0)
645 	{
646 		int w;
647 		gr_get_string_size(&w, NULL, Medals_player->callsign);
648 
649 		x = (gr_screen.clip_width_unscaled - w) / 2;
650 	}
651 	gr_string(x, y, Medals_player->callsign, GR_RESIZE_MENU);
652 }
653 
medal_main_do()654 int medal_main_do()
655 {
656 	int region,selected, k;
657 
658 	// If we don't have a mask, we don't have enough data to do anything with this screen.
659 	if (Medals_bitmap_mask == -1) {
660 		popup_game_feature_not_in_demo();
661 		if (Medals_mode == MM_NORMAL)
662 			gameseq_post_event(GS_EVENT_PREVIOUS_STATE);
663 		// any calling popup function will know to close the screen down
664 		return 0;
665 	}
666 
667 	k = Medals_window.process();
668 
669 	// process an exit command
670 	if ( (k == KEY_ESC) && (Medals_mode == MM_NORMAL) ) {
671 		gameseq_post_event(GS_EVENT_PREVIOUS_STATE);
672 	}
673 
674 	// draw the background medal display case
675 	gr_reset_clip();
676 	GR_MAYBE_CLEAR_RES(Medals_bitmap);
677 	if (Medals_bitmap != -1) {
678 		gr_set_bitmap(Medals_bitmap);
679 		gr_bitmap(0,0,GR_RESIZE_MENU);
680 	}
681 
682 	// check to see if a button was pressed
683 	if ( (k == (KEY_CTRLED|KEY_ENTER)) || (Medals_buttons[gr_screen.res][MEDALS_EXIT].button.pressed()) ) {
684 		gamesnd_play_iface(InterfaceSounds::COMMIT_PRESSED);
685 		if (Medals_mode == MM_NORMAL) {
686 			gameseq_post_event(GS_EVENT_PREVIOUS_STATE);
687 		} else {
688 			// any calling popup function will know to close the screen down
689 			return 0;
690 		}
691 	}
692 
693 	// blit medals also takes care of blitting the rank insignia
694 	blit_medals();
695 	blit_callsign();
696 
697 	region = snazzy_menu_do((ubyte*)Medals_mask->data, Medals_mask_w, Medals_mask_h, Num_medals, Medal_regions, &selected);
698 	if (region == Rank_medal_index)
699 	{
700 		blit_label(Ranks[Player_score->rank].name, 1);
701 	}
702 	else switch (region)
703 	{
704 		case ESC_PRESSED:
705 			if (Medals_mode == MM_NORMAL) {
706 				gameseq_post_event(GS_EVENT_PREVIOUS_STATE);
707 			} else {
708 				// any calling popup function will know to close the screen down
709 				return 0;
710 			}
711 			break;
712 
713 		case -1:
714 			break;
715 
716 		default:
717 			if (Player_score->medal_counts[region] > 0) {
718 				blit_label(Medals[region].get_display_name(), Player_score->medal_counts[region]);
719 			}
720 			break;
721 	} // end switch
722 
723 	Medals_window.draw();
724 
725 	gr_flip();
726 
727 	return 1;
728 }
729 
medal_main_close()730 void medal_main_close()
731 {
732 	if (Init_flags & MEDAL_BITMAP_INIT)
733 		bm_release(Medals_bitmap);
734 
735 	if (Init_flags & MASK_BITMAP_INIT) {
736 		bm_unlock(Medals_bitmap_mask);
737 	}
738 
739 	for (SCP_vector<medal_display_info>::iterator idx = Medal_display_info.begin(); idx != Medal_display_info.end(); ++idx) {
740 		if (idx->bitmap >= 0){
741 			bm_release(idx->bitmap);
742 		}
743 		idx->bitmap = -1;
744 	}
745 
746 	delete[] Medal_regions;
747 	Medal_regions = NULL;
748 
749 	Player_score = NULL;
750 
751 	Medals_window.destroy();
752 
753 	if (Init_flags & MASK_BITMAP_INIT) {
754 		bm_release(Medals_bitmap_mask);
755 	}
756 
757 	snazzy_menu_close();
758 }
759 
760 // function to load in the medals for this player.  It loads medals that the player has (known
761 // by whether or not a non-zero number is present in the player's medal array), then loads the
762 // rank bitmap
init_medal_bitmaps()763 void init_medal_bitmaps()
764 {
765 	int idx;
766 	Assert(Player_score);
767 
768 	for (idx=0; idx<Num_medals; idx++) {
769 		Medal_display_info[idx].bitmap = -1;
770 
771 		if (Player_score->medal_counts[idx] > 0) {
772 			int num_medals;
773 			char filename[MAX_FILENAME_LEN], base[MAX_FILENAME_LEN];
774 
775 			// possibly load a different filename that is specified by the bitmap filename
776 			// for this medal.  if the player has > 1 of these types of medals, then determien
777 			// which of the possible version to use based on the player's count of this medal
778 			strcpy_s( filename, Medals[idx].bitmap );
779 
780 			_splitpath( filename, NULL, NULL, base, NULL );
781 
782 			num_medals = Player_score->medal_counts[idx];
783 
784 			// can't display more than the maximum number of version for this medal
785 			if ( num_medals > Medals[idx].num_versions )
786 				num_medals = Medals[idx].num_versions;
787 
788 			if ( num_medals > 1 ) {
789 				// append the proper character onto the end of the medal filename.  Base version
790 				// has no character. next version is a, then b, etc.
791 				char temp[MAX_FILENAME_LEN];
792 				strcpy_s(temp, base);
793 				sprintf_safe( base, "%s%c", temp, (num_medals-2)+'a');
794 			}
795 
796 			// hi-res support
797 			if (gr_screen.res == GR_1024) {
798 				sprintf_safe( filename, "2_%s", base );
799 			}
800 
801 			// base now contains the actual medal bitmap filename needed to load
802 			// we don't need to pass extension to bm_load anymore, so just use the basename
803 			// as is.
804 			Medal_display_info[idx].bitmap = bm_load((gr_screen.res == GR_1024) ? filename : base);
805 			Assert( Medal_display_info[idx].bitmap != -1 );
806 		}
807 	}
808 
809 	// load up rank insignia
810 	if (gr_screen.res == GR_1024) {
811 		char filename[NAME_LENGTH];
812 		if (snprintf(filename, NAME_LENGTH, "2_%s", Ranks[Player_score->rank].bitmap) >= NAME_LENGTH) {
813 			// Make sure the string is null terminated
814 			filename[NAME_LENGTH - 1] = '\0';
815 		}
816 		Rank_bm = bm_load(filename);
817 	} else {
818 		Rank_bm = bm_load(Ranks[Player_score->rank].bitmap);
819 	}
820 }
821 
init_snazzy_regions()822 void init_snazzy_regions()
823 {
824 	int idx;
825 
826 	// well, we need regions in an array (versus a vector), so...
827 	Assert(Medal_regions == NULL);
828 	Medal_regions = new MENU_REGION[Num_medals];
829 
830 	// snazzy regions for the medals/ranks, etc.
831 	for (idx=0; idx<Num_medals; idx++) {
832 		snazzy_menu_add_region(&Medal_regions[idx], "", idx, 0);
833 	}
834 }
835 
836 // blit the medals -- this includes the rank insignia
blit_medals()837 void blit_medals()
838 {
839 	int idx;
840 
841 	for (idx=0; idx<Num_medals; idx++) {
842 		if (idx != Rank_medal_index && Player_score->medal_counts[idx] > 0) {
843 #ifndef NDEBUG
844 			// this can happen if gimmemedals was used on the medal screen
845 			if (Medal_display_info[idx].bitmap < 0) {
846 				continue;
847 			}
848 #endif
849 			gr_set_bitmap(Medal_display_info[idx].bitmap);
850 			gr_bitmap(Medal_display_info[idx].coords[gr_screen.res].x, Medal_display_info[idx].coords[gr_screen.res].y, GR_RESIZE_MENU);
851 		}
852 	}
853 
854 	// now blit rank, since that "medal" doesn't get loaded (or drawn) the normal way
855 	gr_set_bitmap(Rank_bm);
856 	gr_bitmap(Medal_display_info[Rank_medal_index].coords[gr_screen.res].x, Medal_display_info[Rank_medal_index].coords[gr_screen.res].y, GR_RESIZE_MENU);
857 }
858 
medals_info_lookup(const char * name)859 int medals_info_lookup(const char *name)
860 {
861 	if ( !name ) {
862 		return -1;
863 	}
864 
865 	for (int i = 0; i < Num_medals; i++) {
866 		if ( !stricmp(name, Medals[i].name) ) {
867 			return i;
868 		}
869 	}
870 
871 	return -1;
872 }
873