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 #include <cstdlib>
11 #include <climits>
12 
13 #include "freespace.h"
14 #include "anim/animplay.h"
15 #include "anim/packunpack.h"
16 #include "cmdline/cmdline.h"
17 #include "gamehelp/contexthelp.h"
18 #include "gamesequence/gamesequence.h"
19 #include "gamesnd/eventmusic.h"
20 #include "gamesnd/gamesnd.h"
21 #include "globalincs/alphacolors.h"
22 #include "globalincs/version.h"
23 #include "graphics/generic.h"
24 #include "io/key.h"
25 #include "io/mouse.h"
26 #include "io/timer.h"
27 #include "menuui/fishtank.h"
28 #include "menuui/mainhallmenu.h"
29 #include "menuui/playermenu.h"
30 #include "menuui/snazzyui.h"
31 #include "mission/missioncampaign.h"
32 #include "network/multi.h"
33 #include "network/multi_voice.h"
34 #include "network/multiui.h"
35 #include "network/multiutil.h"
36 #include "parse/parselo.h"
37 #include "playerman/player.h"
38 #include "popup/popup.h"
39 #include "scripting/scripting.h"
40 #include "sound/audiostr.h"
41 #include "utils/Random.h"
42 
43 #ifndef NDEBUG
44 #include "cutscene/movie.h"
45 #include "mission/missionload.h"
46 #endif
47 
48 
49 // A reference to io/keycontrol.cpp
50 extern void game_process_cheats(int k);
51 
52 // forward declaration
53 void parse_one_main_hall(bool replace, int num_resolutions, int &hall_idx, int &res_idx);
54 
55 // ----------------------------------------------------------------------------
56 // MAIN HALL DATA DEFINES
57 //
58 #define MISC_ANIM_MODE_LOOP			0		// loop the animation
59 #define MISC_ANIM_MODE_HOLD			1		// play to the end and hold the animation
60 #define MISC_ANIM_MODE_TIMED		2		// uses timestamps to determine when a finished anim should be checked again
61 #define NUM_REGIONS					7		// (6 + 1 for multiplayer equivalent of campaign room)
62 #define MAIN_HALL_MAX_CHEAT_LEN		40		// cheat buffer length (also maximum cheat length)
63 
64 SCP_vector< SCP_vector<main_hall_defines> > Main_hall_defines;
65 
66 static main_hall_defines *Main_hall = nullptr;
67 
68 static int Main_hall_music_index = -1;
69 
70 int Vasudan_funny = 0;
71 int Vasudan_funny_plate = -1;
72 
73 SCP_string Main_hall_cheat = "";
74 
75 // ----------------------------------------------------------------------------
76 // MISC interface data
77 //
78 // is the main hall inited (for reentrancy)
79 int Main_hall_inited = 0;
80 
81 // handle to the playing music
82 int Main_hall_music_handle = -1;
83 
84 // background bitmap handle
85 int Main_hall_bitmap;
86 
87 // background bitmap dimensions
88 int Main_hall_bitmap_w;
89 int Main_hall_bitmap_h;
90 
91 // background bitmap mask handle
92 int Main_hall_mask;
93 
94 // bitmap struct for th background mask bitmap
95 bitmap *Main_hall_mask_bitmap;
96 
97 // actual data for the background mask bitmap
98 ubyte *Main_hall_mask_data;
99 
100 int Main_hall_mask_w, Main_hall_mask_h;
101 
102 
103 // ----------------------------------------------------------------------------
104 // MOUSE clicking stuff
105 //
106 // indicates whether a right click occured
107 int Main_hall_right_click;
108 
109 // use this to cycle through the selectable regions instead of the mouse's current region
110 int Main_hall_last_clicked_region;
111 
112 // use this to determine how long the cursor has to linger on a region before it starts playing
113 #define MAIN_HALL_REGION_LINGER		175		// in ms
114 int Main_hall_region_linger_stamp = -1;
115 
116 // handle any right clicks which may have occured
117 void main_hall_handle_right_clicks();
118 
119 
120 // ----------------------------------------------------------------------------
121 // RANDOM intercom sounds
122 //
123 
124 // next random intercom sound to play
125 int Main_hall_next_intercom_sound = 0;
126 
127 // delay for the next intercom sound
128 int Main_hall_next_intercom_sound_stamp = -1;
129 
130 // handle to any playing instance of a random intercom sound
131 sound_handle Main_hall_intercom_sound_handle = sound_handle::invalid();
132 
133 // handle any details related to random intercom sounds
134 void main_hall_handle_random_intercom_sounds();
135 
136 
137 // ----------------------------------------------------------------------------
138 // MISC animations
139 //
140 
141 // the misc animations themselves
142 SCP_vector<generic_anim> Main_hall_misc_anim;
143 
144 // render all playing misc animations
145 void main_hall_render_misc_anims(float frametime, bool over_doors);
146 
147 
148 // ----------------------------------------------------------------------------
149 // DOOR animations (not all of these are doors anymore, but they're doorlike _regions_)
150 //
151 #define DOOR_TEXT_X 100
152 #define DOOR_TEXT_Y 450
153 
154 // the door animations themselves
155 SCP_vector<generic_anim> Main_hall_door_anim;
156 
157 // render all playing door animations
158 void main_hall_render_door_anims(float frametime);
159 
160 
161 // ----------------------------------------------------------------------------
162 // SNAZZY MENU stuff
163 //
164 #define NUM_MAIN_HALL_MAX_REGIONS 20
165 
166 // region mask #'s (identifiers)
167 #define EXIT_REGION				0
168 #define BARRACKS_REGION			1
169 #define READY_ROOM_REGION		2
170 #define TECH_ROOM_REGION		3
171 #define OPTIONS_REGION			4
172 #define CAMPAIGN_ROOM_REGION	5
173 #define MULTIPLAYER_REGION		10
174 #define LOAD_MISSION_REGION		11
175 #define QUICK_START_REGION		12
176 #define SKILL_LEVEL_REGION		13
177 #define SCRIPT_REGION			14
178 #define START_REGION			15
179 
180 struct main_hall_region_info {
181 	int mask;
182 	const char *name;
183 };
184 
185 main_hall_region_info Main_hall_region_map[] = {
186 	{ EXIT_REGION, "Exit" },
187 	{ BARRACKS_REGION, "Barracks" },
188 	{ READY_ROOM_REGION, "Readyroom" },
189 	{ TECH_ROOM_REGION, "Techroom" },
190 	{ OPTIONS_REGION, "Options" },
191 	{ CAMPAIGN_ROOM_REGION, "Campaigns" },
192 	{ MULTIPLAYER_REGION, "Multiplayer" },
193 	{ LOAD_MISSION_REGION, "Load Mission" },
194 	{ QUICK_START_REGION, "Quickstart" },
195 	{ SKILL_LEVEL_REGION, "Skilllevel" },
196 	{ SCRIPT_REGION, "Script" },
197 	{ START_REGION, "Start" },
198 	{ -1, nullptr }
199 };
200 
201 // all the menu regions in the main hall
202 MENU_REGION Main_hall_region[NUM_MAIN_HALL_MAX_REGIONS];
203 
204 // region over which the mouse is currently residing, or -1 if over no region
205 // NOTE : you should nevery change this directly. Always use main_hall_handle_mouse_location(int)
206 //        to do this. Otherwise, the door opening and closing animations will get screwed up
207 int Main_hall_mouse_region;
208 
209 // set this to skip a frame
210 int Main_hall_frame_skip;
211 
212 // do any necessary processing based upon the mouse location
213 void main_hall_handle_mouse_location(int cur_region);
214 
215 // if the mouse has moved off of the currently active region, handle the anim accordingly
216 void main_hall_mouse_release_region(int region);
217 
218 // if the mouse has moved on this region, handle it accordingly
219 void main_hall_mouse_grab_region(int region);
220 
221 
222 // ----------------------------------------------------------------------------
223 // SOUND data / handlers
224 // -
225 
226 // toaster oven room sound idex
227 #define TOASTER_REGION		3
228 
229 // everyone's favorite desk guardian
230 #define ALLENDER_REGION		4
231 
232 // handles to the sound instances of the doors opening/closing
233 SCP_vector<std::pair<game_snd*, sound_handle>> Main_hall_door_sound_handles;
234 
235 // handles to the sound instances of the misc anims
236 SCP_list<std::pair<game_snd*, sound_handle>> Main_hall_misc_sound_handles;
237 
238 // sound handle for looping ambient sound
239 sound_handle Main_hall_ambient_loop = sound_handle::invalid();
240 
241 // this generalizes the "don't cut off the glow sounds (requested by Dan)" comment
242 SCP_vector<game_snd*> Main_hall_sounds_to_not_truncate;
main_hall_can_truncate(const game_snd * snd)243 bool main_hall_can_truncate(const game_snd *snd)
244 {
245 	return std::find(Main_hall_sounds_to_not_truncate.begin(), Main_hall_sounds_to_not_truncate.end(), snd) == Main_hall_sounds_to_not_truncate.end();
246 }
247 
248 // cull any door sounds that have finished playing
249 void main_hall_cull_door_sounds();
250 
251 // to determine if we should continue playing sounds and random animations
252 static int Main_hall_paused = 0;
253 
254 
255 // ----------------------------------------------------------------------------
256 // warning/notification messages
257 //
258 #define MAIN_HALL_NOTIFY_TIME	3500
259 
260 // timestamp for the notification messages
261 int Main_hall_notify_stamp = -1;
262 
263 // text to display as the current notification message
264 char Main_hall_notify_text[300]="";
265 
266 // set the current notification string and the associated timestamp
267 void main_hall_set_notify_string(const char *str);
268 
269 // handle any drawing, culling, etc of notification messages
270 void main_hall_notify_do();
271 
272 
273 // ----------------------------------------------------------------------------
274 // MISC functions
275 //
276 
277 // upper _RIGHT_ corner for the version text
278 #define MAIN_HALL_VERSION_X		630
279 #define MAIN_HALL_VERSION_Y		467
280 
281 // main hall help overlay ID
282 int Main_hall_overlay_id;
283 
284 // blit the freespace version #
285 void main_hall_blit_version();
286 
287 // blit any necessary tooltips
288 void main_hall_maybe_blit_tooltips();
289 
290 // shader for behind tooltips
291 shader Main_hall_tooltip_shader;
292 
293 // num pixels shader is above/below tooltip text
294 static int Main_hall_default_tooltip_padding[GR_NUM_RESOLUTIONS] = {
295 	4,		// GR_640
296 	7,		// GR_1024
297 };
298 static int Main_hall_f1_text_frame = 0;
299 static int F1_text_done = 0;
300 
301 // "press f1" for help stuff
302 #define MAIN_HALL_HELP_TIME		5000
303 int Main_hall_help_stamp = -1;
304 void main_hall_process_help_stuff();
305 
306 
307 // ----------------------------------------------------------------------------
308 // VOICE RECORDING STUFF
309 //
310 
311 // are we currently recording voice?
312 int Recording = 0;
313 
314 /*
315  * Called when multiplayer clicks on the ready room door.  May pop up dialog depending on network
316  * connection status and errors
317  */
main_hall_do_multi_ready()318 void main_hall_do_multi_ready()
319 {
320 	int error;
321 
322 	error = psnet_get_network_status();
323 
324 	switch (error) {
325 		case NETWORK_ERROR_NO_TYPE:
326 			popup( PF_USE_AFFIRMATIVE_ICON | PF_NO_NETWORKING, 1, POPUP_OK, XSTR( "You have not defined your type of Internet connection.  Please run the Launcher, hit the setup button, and go to the Network tab and choose your connection type.", 360));
327 			break;
328 
329 		case NETWORK_ERROR_NO_WINSOCK:
330 			popup( PF_USE_AFFIRMATIVE_ICON | PF_NO_NETWORKING, 1, POPUP_OK, XSTR( "Winsock is not installed.  You must have TCP/IP and Winsock installed to play multiplayer FreeSpace.", 361));
331 			break;
332 
333 		case NETWORK_ERROR_NO_PROTOCOL:
334 			popup( PF_USE_AFFIRMATIVE_ICON | PF_NO_NETWORKING, 1, POPUP_OK, XSTR( "TCP/IP protocol not found.  This protocol is required for multiplayer FreeSpace.", 1602));
335 			break;
336 
337 		case NETWORK_ERROR_CONNECT_TO_ISP:
338 			popup( PF_USE_AFFIRMATIVE_ICON | PF_NO_NETWORKING, 1, POPUP_OK, XSTR( "You have selected Dial Up Networking as your type of connection to the Internet.  You are not currently connected.  You must connect to your ISP before continuing on past this point.", 363));
339 			break;
340 
341 		case NETWORK_ERROR_LAN_AND_RAS:
342 			popup( PF_USE_AFFIRMATIVE_ICON | PF_NO_NETWORKING, 1, POPUP_OK, XSTR( "You have indicated that you use a LAN for networking.  You also appear to be dialed into your ISP.  Please disconnect from your service provider, or choose Dial Up Networking.", 364));
343 			break;
344 
345 		case NETWORK_ERROR_NONE:
346 		default:
347 			break;
348 	}
349 
350 	// if our selected protocol is not active
351 	if ( !psnet_is_active() ) {
352 		if (Psnet_failure_code == WSAEADDRINUSE) {
353 			popup( PF_USE_AFFIRMATIVE_ICON | PF_NO_NETWORKING, 1, POPUP_OK, XSTR( "You have selected TCP/IP for multiplayer FreeSpace, but the TCP socket is already in use.  Check for another instance and/or use the \"-port <port_num>\" command line option to select an available port.", 1604));
354 		} else {
355 			popup( PF_USE_AFFIRMATIVE_ICON | PF_NO_NETWORKING, 1, POPUP_OK, XSTR( "You have selected TCP/IP for multiplayer FreeSpace, but the TCP/IP protocol was not detected on your machine.", 362));
356 		}
357 		return;
358 	}
359 
360 	if (error != NETWORK_ERROR_NONE) {
361 		return;
362 	}
363 
364 	// 7/9/98 -- MWA.  Deal with the connection speed issue.  make a call to the multiplayer code to
365 	// determine is a valid connection setting exists
366 	if (Multi_connection_speed == CONNECTION_SPEED_NONE) {
367 		popup( PF_USE_AFFIRMATIVE_ICON | PF_NO_NETWORKING, 1, POPUP_OK, XSTR( "You must define your connection speed.  Please run the Launcher, hit the setup button, and go to the Network tab and choose your connection speed.", 986) );
368 		return;
369 	}
370 
371 	// Switch to multi here once we're sure that a multi state will be set
372 	Game_mode = GM_MULTIPLAYER;
373 
374 	// go to parallax online
375 	if (Multi_options_g.pxo == 1) {
376 		gameseq_post_event(GS_EVENT_PXO);
377 	} else {
378 		// go to the regular join game screen
379 		gameseq_post_event(GS_EVENT_MULTI_JOIN_GAME);
380 	}
381 }
382 
383 
384 // blit some small color indicators to show whether ships.tbl and weapons.tbl are valid
385 // green == valid, red == invalid.
386 // ships.tbl will be on the left, weapons.tbl on the right
main_hall_blit_table_status()387 void main_hall_blit_table_status()
388 {
389 	// blit ship table status
390 	gr_set_color_fast(Game_ships_tbl_valid ? &Color_bright_green : &Color_bright_red);
391 	gr_rect(1, gr_screen.max_h_unscaled_zoomed - 1, 2, 2, GR_RESIZE_MENU_ZOOMED);
392 
393 	// blit weapon table status
394 	gr_set_color_fast(Game_weapons_tbl_valid ? &Color_bright_green : &Color_bright_red);
395     gr_rect(3, gr_screen.max_h_unscaled_zoomed - 1, 2, 2, GR_RESIZE_MENU_ZOOMED);
396 }
397 
398 /**
399  * Bash the player to a specific mission in a campaign
400  */
main_hall_campaign_cheat()401 void main_hall_campaign_cheat()
402 {
403 	char *ret = popup_input(0, XSTR("Enter mission name.\n\n* This will destroy all legitimate progress in this campaign. *", 1605));
404 
405 	// yay
406 	if (ret != nullptr) {
407 		mission_campaign_jump_to_mission(ret);
408 	}
409 }
410 
411 // -------------------------------------------------------------------------------------------------------------------
412 // FUNCTION DEFINITIONS BEGIN
413 //
414 
415 /**
416  * Initialize the main hall proper
417  *
418  * @param main_hall_name Name of main hall to initialise
419  */
main_hall_init(const SCP_string & main_hall_name)420 void main_hall_init(const SCP_string &main_hall_name)
421 {
422 	BM_TYPE bg_type;
423 	if (Main_hall_inited) {
424 		return;
425 	}
426 
427 	// gameseq_post_event(GS_EVENT_SCRIPTING);
428 
429 	int idx;
430 	SCP_string main_hall_to_load;
431 
432 	// reparse the table here if the relevant cmdline flag is set
433 	if (Cmdline_reparse_mainhall) {
434 		main_hall_table_init();
435 	}
436 
437 	// sanity checks
438 	if (Main_hall_defines.empty()) {
439 		Error(LOCATION, "No main halls were loaded to initialize.");
440 	} else if (main_hall_name == "") {
441 		// we were passed a blank main hall name, so load the first available main hall
442 		main_hall_get_name(main_hall_to_load, 0);
443 	} else if (main_hall_get_pointer(main_hall_name) == nullptr) {
444 		Warning(LOCATION, "Tried to load a main hall called '%s', but it does not exist; loading first available main hall.", main_hall_name.c_str());
445 		main_hall_get_name(main_hall_to_load, 0);
446 	} else {
447 		main_hall_to_load = main_hall_name;
448 	}
449 
450 	// if we're switching to a different mainhall, stop the ambient (it will be started again promptly)
451 	if (Main_hall && Main_hall->name != main_hall_to_load) {
452 		main_hall_stop_ambient();
453 	}
454 
455 	// if we're switching to a different mainhall we may need to change music
456 	if (main_hall_get_music_index(main_hall_get_index(main_hall_to_load)) != Main_hall_music_index) {
457 		main_hall_stop_music(true);
458 	}
459 
460 	// create the snazzy interface and load up the info from the table
461 	snazzy_menu_init();
462 
463 	// assign the proper main hall data
464 	Main_hall = main_hall_get_pointer(main_hall_to_load);
465 	Assertion(Main_hall != nullptr, "Failed to obtain pointer to main hall '%s'; get a coder!\n", main_hall_to_load.c_str());
466 
467 	// check if we have to change the ready room's description
468 	if(Main_hall->default_readyroom) {
469 		if (Player->flags & PLAYER_FLAGS_IS_MULTI) {
470 			Main_hall->regions[2].description = XSTR( "Multiplayer - Start or join a multiplayer game", 359);
471 		} else {
472 			Main_hall->regions[2].description = XSTR( "Ready room - Start or continue a campaign", 355);
473 		}
474 	}
475 
476 	// Read the menu regions from mainhall.tbl
477 	SCP_vector<main_hall_region>::iterator it;
478 	for (it = Main_hall->regions.begin(); Main_hall->regions.end() != it; ++it) {
479 		snazzy_menu_add_region(&Main_hall_region[it - Main_hall->regions.begin()], it->description.c_str(), it->mask, it->key, interface_snd_id());
480 	}
481 
482 	// init tooltip shader						// nearly black
483 	gr_create_shader(&Main_hall_tooltip_shader, 5, 5, 5, 168);
484 
485 	// are we funny?
486 	if (Vasudan_funny && main_hall_is_vasudan()) {
487 		if (!stricmp(Main_hall->bitmap.c_str(), "vhall")) {
488 			Main_hall->door_sounds.at(OPTIONS_REGION).at(0) = InterfaceSounds::VASUDAN_BUP;
489 			Main_hall->door_sounds.at(OPTIONS_REGION).at(1) = InterfaceSounds::VASUDAN_BUP;
490 
491 			// set head anim. hehe
492 			Main_hall->door_anim_name.at(OPTIONS_REGION) = "vhallheads";
493 
494 			// set the background
495 			Main_hall->bitmap = "vhallhead";
496 		} else if (!stricmp(Main_hall->bitmap.c_str(), "2_vhall")) {
497 			Main_hall->door_sounds.at(OPTIONS_REGION).at(0) = InterfaceSounds::VASUDAN_BUP;
498 			Main_hall->door_sounds.at(OPTIONS_REGION).at(1) = InterfaceSounds::VASUDAN_BUP;
499 
500 			// set head anim. hehe
501 			Main_hall->door_anim_name.at(OPTIONS_REGION) = "2_vhallheads";
502 
503 			// set the background
504 			Main_hall->bitmap = "2_vhallhead";
505 		}
506 	}
507 
508 	Main_hall_bitmap_w = -1;
509 	Main_hall_bitmap_h = -1;
510 
511 	// load the background bitmap
512 	Main_hall_bitmap = bm_load(Main_hall->bitmap);
513 	if (Main_hall_bitmap < 0) {
514 		nprintf(("General","WARNING! Couldn't load main hall background bitmap %s\n", Main_hall->bitmap.c_str()));
515 	} else {
516 		bm_get_info(Main_hall_bitmap, &Main_hall_bitmap_w, &Main_hall_bitmap_h);
517 	}
518 	bg_type = bm_get_type(Main_hall_bitmap);
519 
520 	Main_hall_mask_w = -1;
521 	Main_hall_mask_h = -1;
522 
523 	// load the mask
524 	Main_hall_mask = bm_load(Main_hall->mask);
525 	if (Main_hall_mask < 0) {
526 		nprintf(("General","WARNING! Couldn't load main hall background mask %s\n", Main_hall->mask.c_str()));
527 		if (gr_screen.res == 0) {
528 			Error(LOCATION,"Could not load in main hall mask '%s'!\n\n(This error most likely means that you are missing required 640x480 interface art.)", Main_hall->mask.c_str());
529 		} else {
530 			Error(LOCATION,"Could not load in main hall mask '%s'!\n\n(This error most likely means that you are missing required 1024x768 interface art.)", Main_hall->mask.c_str());
531 		}
532 	} else {
533 		// get a pointer to bitmap by using bm_lock(), so we can feed it to he snazzy menu system
534 		Main_hall_mask_bitmap = bm_lock(Main_hall_mask, 8, BMP_AABITMAP | BMP_MASK_BITMAP);
535 		Main_hall_mask_data = (ubyte*)Main_hall_mask_bitmap->data;
536 		bm_get_info(Main_hall_mask, &Main_hall_mask_w, &Main_hall_mask_h);
537 	}
538 
539 	// make sure the zoom area is completely within the background bitmap
540 	if (Main_hall->zoom_area_width > Main_hall_bitmap_w) {
541 		Main_hall->zoom_area_width = Main_hall_bitmap_w;
542 	}
543 	if (Main_hall->zoom_area_height > Main_hall_bitmap_h) {
544 		Main_hall->zoom_area_height = Main_hall_bitmap_h;
545 	}
546 
547 	// get the default value for tooltip padding if necessary
548 	if (Main_hall->tooltip_padding == -1) {
549 		Main_hall->tooltip_padding = Main_hall_default_tooltip_padding[gr_get_resolution_class(Main_hall_bitmap_w, Main_hall_bitmap_h)];
550 	}
551 
552 	// In case we're re-entering the mainhall
553 	Main_hall_misc_anim.clear();
554 
555 	// load up the misc animations, and nullify all the delay timestamps for the misc animations
556 	for (idx = 0; idx < Main_hall->num_misc_animations; idx++) {
557 		generic_anim temp_anim;
558 		generic_anim_init(&temp_anim, Main_hall->misc_anim_name.at(idx));
559 		Main_hall_misc_anim.push_back(temp_anim);
560 		Main_hall_misc_anim.at(idx).ani.bg_type = bg_type;
561 		if (generic_anim_stream(&Main_hall_misc_anim.at(idx)) == -1) {
562 			nprintf(("General","WARNING!, Could not load misc %s anim in main hall\n",Main_hall->misc_anim_name.at(idx).c_str()));
563 		} else {
564 			// start paused
565 			if (Main_hall->misc_anim_modes.at(idx) == MISC_ANIM_MODE_HOLD)
566 				Main_hall_misc_anim.at(idx).direction |= GENERIC_ANIM_DIRECTION_NOLOOP;
567 		}
568 
569 		// null out the delay timestamps
570 		Main_hall->misc_anim_delay.at(idx).at(0) = -1;
571 
572 		// start paused
573 		Main_hall->misc_anim_paused.at(idx) = true;
574 	}
575 
576 	// In case we're re-entering the mainhall
577 	Main_hall_door_anim.clear();
578 
579 	// load up the door animations
580 	for (idx = 0; idx < Main_hall->num_door_animations; idx++) {
581 		generic_anim temp_anim;
582 		generic_anim_init(&temp_anim, Main_hall->door_anim_name.at(idx));
583 		Main_hall_door_anim.push_back(temp_anim);
584 		Main_hall_door_anim.at(idx).ani.bg_type = bg_type;
585 		if (generic_anim_stream(&Main_hall_door_anim.at(idx)) == -1) {
586 			nprintf(("General","WARNING!, Could not load door anim %s in main hall\n",Main_hall->door_anim_name.at(idx).c_str()));
587 		} else {
588 			Main_hall_door_anim.at(idx).direction = GENERIC_ANIM_DIRECTION_BACKWARDS | GENERIC_ANIM_DIRECTION_NOLOOP;
589 		}
590 	}
591 
592 	// load in help overlay bitmap
593 	if (!Main_hall->help_overlay_name.empty()) {
594 		Main_hall_overlay_id = help_overlay_get_index(Main_hall->help_overlay_name.c_str());
595 	} else if (main_hall_id() == 0) {
596 		Main_hall_overlay_id = help_overlay_get_index(MH_OVERLAY);
597 	} else {
598 		Main_hall_overlay_id = help_overlay_get_index(MH2_OVERLAY);
599 	}
600 	help_overlay_set_state(Main_hall_overlay_id,gr_screen.res,0);
601 
602 	// check to see if the "very first pilot" flag is set, and load the overlay if so
603 	if (!F1_text_done) {
604 		if (Main_hall_f1_text_frame == 0) {
605 			Main_hall_help_stamp = timestamp(MAIN_HALL_HELP_TIME);
606 		} else {
607 			F1_text_done = 1;
608 		}
609 	}
610 
611 	Main_hall_region_linger_stamp = -1;
612 
613 	// initialize door sound handles
614 	Main_hall_door_sound_handles.clear();
615 	for (idx = 0; idx < Main_hall->num_door_animations; idx++) {
616 		Main_hall_door_sound_handles.emplace_back(nullptr, sound_handle::invalid());
617 	}
618 
619 	// skip the first frame
620 	Main_hall_frame_skip = 1;
621 
622 	// initialize the music
623 	main_hall_start_music();
624 
625 	// initialize the main hall notify text
626 	Main_hall_notify_stamp = 1;
627 
628 	// initialize the random intercom sound stuff
629 	Main_hall_next_intercom_sound = 0;
630 	Main_hall_next_intercom_sound_stamp = -1;
631 	Main_hall_intercom_sound_handle     = sound_handle::invalid();
632 
633 	// set the placement of the mouse cursor (start at the ready room)
634 	Main_hall_mouse_region = -1;
635 	Main_hall_last_clicked_region = READY_ROOM_REGION;
636 
637 	Main_hall_inited = 1;
638 
639 	// determine if we have a right click
640 	Main_hall_right_click = mouse_down(MOUSE_RIGHT_BUTTON);
641 }
642 
643 /**
644  * Exit the game
645  *
646  * @note In Debug mode, the confirmation popup does not get used, and the game usefully exits immediately
647  */
main_hall_exit_game()648 void main_hall_exit_game()
649 {
650 #if defined(NDEBUG)
651 	int choice;
652 
653 	// stop music first
654 	main_hall_stop_music(true);
655 	main_hall_stop_ambient();
656 	choice = popup( PF_NO_NETWORKING | PF_BODY_BIG, 2, POPUP_NO, POPUP_YES, XSTR( "Exit Game?", 365));
657 	if (choice == 1) {
658 		gameseq_post_event(GS_EVENT_QUIT_GAME);
659 	} else {
660 		main_hall_start_music();
661 		main_hall_start_ambient();
662 	}
663 #else
664 	gameseq_post_event(GS_EVENT_QUIT_GAME);
665 #endif
666 }
667 
668 /**
669  * Do a frame for the main hall
670  *
671  * @param frametime Animation frame time
672  */
main_hall_do(float frametime)673 void main_hall_do(float frametime)
674 {
675 	int code, key, snazzy_action, region_action = -1;
676 	SCP_vector<main_hall_region>::iterator it;
677 
678 	// set the screen scale to the main hall's dimensions
679 	gr_set_screen_scale(Main_hall_bitmap_w, Main_hall_bitmap_h, Main_hall->zoom_area_width, Main_hall->zoom_area_height);
680 
681 	// need to ensure ambient is playing, since it may be stopped by a playing movie
682 	main_hall_start_ambient();
683 
684 	// handle any random intercom sound details
685 	main_hall_handle_random_intercom_sounds();
686 
687 	// handle any mouse clicks
688 	main_hall_handle_right_clicks();
689 
690 	// handle any sound details
691 	main_hall_cull_door_sounds();
692 
693 	// do any campaign load failure handling
694 	mission_campaign_load_failure_popup();
695 
696 	// process any keypresses/mouse events
697 	snazzy_action = -1;
698 	code = snazzy_menu_do(Main_hall_mask_data, Main_hall_mask_w, Main_hall_mask_h, (int)Main_hall->regions.size(), Main_hall_region, &snazzy_action, 1, &key);
699 
700 	if (key) {
701 		game_process_cheats(key);
702 
703 		Main_hall_cheat += (char) key_to_ascii(key);
704 		if(Main_hall_cheat.size() > MAIN_HALL_MAX_CHEAT_LEN) {
705 			Main_hall_cheat = Main_hall_cheat.substr(Main_hall_cheat.size() - MAIN_HALL_MAX_CHEAT_LEN);
706 		}
707 
708 		int cur_frame;
709 		float anim_time;
710 		bool cheat_anim_found, cheat_found = false;
711 
712 		for (int c_idx = 0; c_idx < (int) Main_hall->cheat.size(); c_idx++) {
713 			cheat_anim_found = false;
714 
715 			// TODO change way cheat anims are loaded to work with apngs
716 			// maybe load both cheat & normal, advance frames in lockstep, display which one you want
717 			if(Main_hall_cheat.find(Main_hall->cheat.at(c_idx)) != SCP_string::npos) {
718 				cheat_found = true;
719 				// switch animations
720 
721 				for (int idx = 0; idx < Main_hall->num_misc_animations; idx++) {
722 					if (Main_hall->misc_anim_name.at(idx) == Main_hall->cheat_anim_from.at(c_idx)) {
723 						Main_hall->misc_anim_name.at(idx) = Main_hall->cheat_anim_to.at(c_idx);
724 
725 						cur_frame = Main_hall_misc_anim.at(idx).current_frame;
726 						anim_time = Main_hall_misc_anim.at(idx).anim_time;
727 
728 						generic_anim_unload(&Main_hall_misc_anim.at(idx));
729 						generic_anim_init(&Main_hall_misc_anim.at(idx), Main_hall->misc_anim_name.at(idx));
730 
731 						if (generic_anim_stream(&Main_hall_misc_anim.at(idx)) == -1) {
732 							nprintf(("General","WARNING! Could not load misc %s anim in main hall\n", Main_hall->misc_anim_name.at(idx).c_str()));
733 						} else {
734 							// start paused
735 							if (Main_hall->misc_anim_modes.at(idx) == MISC_ANIM_MODE_HOLD)
736 								Main_hall_misc_anim.at(idx).direction |= GENERIC_ANIM_DIRECTION_NOLOOP;
737 						}
738 
739 						// TODO: generic_anim_skip_to(cur_frame, anim_time);
740 						Main_hall_misc_anim.at(idx).current_frame = cur_frame;
741 						Main_hall_misc_anim.at(idx).anim_time = anim_time;
742 
743 						// null out the delay timestamps
744 						Main_hall->misc_anim_delay.at(idx).at(0) = -1;
745 
746 						cheat_anim_found = true;
747 						break;
748 					}
749 				}
750 
751 				if (!cheat_anim_found) {
752 					for (int idx = 0; idx < Main_hall->num_door_animations; idx++) {
753 						if (Main_hall->door_anim_name.at(idx) == Main_hall->cheat_anim_from.at(c_idx)) {
754 							Main_hall->door_anim_name.at(idx) = Main_hall->cheat_anim_to.at(c_idx);
755 
756 							cur_frame = Main_hall_door_anim.at(idx).current_frame;
757 							anim_time = Main_hall_door_anim.at(idx).anim_time;
758 
759 							generic_anim_unload(&Main_hall_door_anim.at(idx));
760 							generic_anim_init(&Main_hall_door_anim.at(idx), Main_hall->door_anim_name.at(idx));
761 
762 							if (generic_anim_stream(&Main_hall_door_anim.at(idx)) == -1) {
763 								nprintf(("General","WARNING! Could not load door anim %s in main hall\n", Main_hall->door_anim_name.at(idx).c_str()));
764 							} else {
765 								Main_hall_door_anim.at(idx).direction = GENERIC_ANIM_DIRECTION_BACKWARDS | GENERIC_ANIM_DIRECTION_NOLOOP;
766 							}
767 
768 							// TODO: generic_anim_skip_to(cur_frame, anim_time);
769 							Main_hall_door_anim.at(idx).current_frame = cur_frame;
770 							Main_hall_door_anim.at(idx).anim_time = anim_time;
771 
772 							cheat_anim_found = true;
773 							break;
774 						}
775 					}
776 				}
777 
778 				if (!cheat_anim_found) {
779 					// Note: This can also happen if the cheat triggers a second time since the animations are already switched at that point.
780 					nprintf(("General", "Could not find animation '%s' for cheat '%s'!", Main_hall->cheat_anim_from.at(c_idx).c_str(), Main_hall->cheat.at(c_idx).c_str()));
781 				}
782 			}
783 		}
784 
785 		if(cheat_found) {
786 			// Found a cheat, clear the buffer.
787 
788 			Main_hall_cheat = "";
789 		}
790 	}
791 
792 	switch(key) {
793 		case KEY_ENTER:
794 			snazzy_action = SNAZZY_CLICKED;
795 			break;
796 		case KEY_F3:
797 			gamesnd_play_iface(InterfaceSounds::IFACE_MOUSE_CLICK);
798 			gameseq_post_event(GS_EVENT_LAB);
799 			break;
800 	#ifndef NDEBUG
801 		case KEY_1:
802 			// no soup for you!
803 			movie::play("endprt2b.mve");
804 			break;
805 		case KEY_2:
806 			// no soup for you!
807 			movie::play_two("endprt2a.mve", "endprt2b.mve");
808 			break;
809 		case KEY_3:
810 			main_hall_campaign_cheat();
811 			break;
812 	#endif
813 	}
814 
815 	// do any processing based upon what happened to the snazzy menu
816 	switch (snazzy_action) {
817 		case SNAZZY_OVER:
818 			for (it = Main_hall->regions.begin(); Main_hall->regions.end() != it; ++it) {
819 				if (it->mask == code) {
820 					main_hall_handle_mouse_location((int)std::distance(Main_hall->regions.begin(), it));
821 					break;
822 				}
823 			}
824 
825 			break;
826 
827 		case SNAZZY_CLICKED:
828 			if (code == ESC_PRESSED) {
829 				region_action = ESC_PRESSED;
830 			} else {
831 				if (code == -1) {
832 					// User didn't click on a valid button, just ignore the event
833 					break;
834 				}
835 
836 				for (it = Main_hall->regions.begin(); Main_hall->regions.end() != it; ++it) {
837 					if (it->mask == code) {
838 						region_action = it->action;
839 						break;
840 					}
841 				}
842 
843 				if (region_action == -1) {
844 					Error(LOCATION, "Region %d doesn't have an action!", code);
845 				} else if (region_action == START_REGION) {
846 					if (Player->flags & PLAYER_FLAGS_IS_MULTI) {
847 						region_action = MULTIPLAYER_REGION;
848 					} else {
849 						region_action = READY_ROOM_REGION;
850 					}
851 				}
852 			}
853 
854 			switch (region_action) {
855 				// clicked on the exit region
856 				case EXIT_REGION:
857 					gamesnd_play_iface(InterfaceSounds::IFACE_MOUSE_CLICK);
858 					main_hall_exit_game();
859 					break;
860 
861 				// clicked on the readyroom region
862 				case READY_ROOM_REGION:
863 					// Make sure we aren't in multi mode.
864 					Player->flags &= ~PLAYER_FLAGS_IS_MULTI;
865 					Game_mode = GM_NORMAL;
866 
867 					gameseq_post_event(GS_EVENT_NEW_CAMPAIGN);
868 					gamesnd_play_iface(InterfaceSounds::IFACE_MOUSE_CLICK);
869 					break;
870 
871 				// clicked on the tech room region
872 				case TECH_ROOM_REGION:
873 					gamesnd_play_iface(InterfaceSounds::IFACE_MOUSE_CLICK);
874 					gameseq_post_event(GS_EVENT_TECH_MENU);
875 					break;
876 
877 				// clicked on the options region
878 				case OPTIONS_REGION:
879 					gamesnd_play_iface(InterfaceSounds::IFACE_MOUSE_CLICK);
880 					gameseq_post_event(GS_EVENT_OPTIONS_MENU);
881 					break;
882 
883 				// clicked on the campaign toom region
884 				case CAMPAIGN_ROOM_REGION:
885 					gamesnd_play_iface(InterfaceSounds::IFACE_MOUSE_CLICK);
886 					gameseq_post_event(GS_EVENT_CAMPAIGN_ROOM);
887 					break;
888 
889 				// clicked on the multiplayer region
890 				case MULTIPLAYER_REGION:
891 					// Make sure we are in multi mode.
892 					Player->flags |= PLAYER_FLAGS_IS_MULTI;
893 
894 					// This function will post the correct state (or no state on error)
895 					main_hall_do_multi_ready();
896 					break;
897 
898 				// load mission key was pressed
899 				case LOAD_MISSION_REGION:
900 					break;
901 
902 				// quick start a game region
903 				case QUICK_START_REGION:
904 			#if !defined(NDEBUG)
905 					if (Num_recent_missions > 0) {
906 						strcpy_s(Game_current_mission_filename, Recent_missions[0]);
907 					} else {
908 						if (mission_load_up_campaign()) {
909 							main_hall_set_notify_string(XSTR( "Campaign file is currently unavailable", 1606));
910 						}
911 						strcpy_s(Game_current_mission_filename, Campaign.missions[0].name);
912 					}
913 					Campaign.current_mission = -1;
914 					gameseq_post_event(GS_EVENT_START_GAME_QUICK);
915 			#endif
916 					break;
917 
918 				// clicked on the barracks region
919 				case BARRACKS_REGION:
920 					gamesnd_play_iface(InterfaceSounds::IFACE_MOUSE_CLICK);
921 					gameseq_post_event(GS_EVENT_BARRACKS_MENU);
922 					break;
923 
924 				// increate the skill level
925 				case SKILL_LEVEL_REGION:
926 					char temp[100];
927 					game_increase_skill_level();
928 					sprintf(temp, XSTR( "Skill level set to %s.", 370), Skill_level_names(Game_skill_level));
929 					main_hall_set_notify_string(temp);
930 					break;
931 
932 				// escape was pressed
933 				case ESC_PRESSED:
934 					// if there is a help overlay active, then don't quit the game - just kill the overlay
935 					if (!help_overlay_active(Main_hall_overlay_id)) {
936 						gamesnd_play_iface(InterfaceSounds::IFACE_MOUSE_CLICK);
937 						main_hall_exit_game();
938 					} else { // kill the overlay
939 						help_overlay_set_state(Main_hall_overlay_id,gr_screen.res,0);
940 					}
941 					break;
942 
943 				// custom action
944 				case SCRIPT_REGION:
945 					const char *lua = it->lua_action.c_str();
946 					bool success    = Script_system.EvalString(lua, lua);
947 					if (!success)
948 						Warning(LOCATION,
949 						        "mainhall '+Door Action / $Script' failed to evaluate \"%s\"; check your syntax", lua);
950 					break;
951 			} // END switch (code)
952 
953 			// if the escape key wasn't pressed handle any mouse position related events
954 			if (code != ESC_PRESSED) {
955 				main_hall_handle_mouse_location((region_action == -1 ? -1 : (int)std::distance(Main_hall->regions.begin(), it)));
956 			}
957 			break;
958 
959 			default:
960 				main_hall_handle_mouse_location(-1);
961 				break;
962 	} // END switch (snazzy_action)
963 
964 	if (mouse_down(MOUSE_LEFT_BUTTON)) {
965 		help_overlay_set_state(Main_hall_overlay_id, main_hall_get_overlay_resolution_index(), 0);
966 	}
967 
968 	// draw the background bitmap
969 	gr_reset_clip();
970 	GR_MAYBE_CLEAR_RES(Main_hall_bitmap);
971 	if (Main_hall_bitmap >= 0) {
972 		gr_set_bitmap(Main_hall_bitmap);
973 		gr_bitmap(0, 0, GR_RESIZE_MENU);
974 	}
975 
976 	// render misc animations
977 	main_hall_render_misc_anims(frametime, false);
978 
979 	// render door animtions
980 	main_hall_render_door_anims(frametime);
981 
982 	// render misc animations (over doors)
983 	main_hall_render_misc_anims(frametime, true);
984 
985 	// blit any appropriate tooltips
986 	main_hall_maybe_blit_tooltips();
987 
988 	// fishtank
989 	fishtank_process();
990 
991 	// draw any pending notification messages
992 	main_hall_notify_do();
993 
994 	// process any help "hit f1" timestamps and display any messages if necessary
995 	if (!F1_text_done) {
996 		main_hall_process_help_stuff();
997 	}
998 
999 	// blit help overlay if active
1000 	help_overlay_maybe_blit(Main_hall_overlay_id, main_hall_get_overlay_resolution_index());
1001 
1002 	// blit the freespace version #
1003 	main_hall_blit_version();
1004 
1005 	// blit ship and weapon table status
1006 #ifndef NDEBUG
1007 	main_hall_blit_table_status();
1008 #endif
1009 
1010 	gr_flip();
1011 	gr_reset_screen_scale();
1012 
1013 	// see if we have a missing campaign and force the player to select a new campaign if so
1014 	extern bool Campaign_room_no_campaigns;
1015 	if ( !(Player->flags & PLAYER_FLAGS_IS_MULTI) && Campaign_file_missing && !Campaign_room_no_campaigns ) {
1016 		int rc = popup(0, 3, XSTR("Go to Campaign Room", 1607), XSTR("Select another pilot", 1608), XSTR("Exit Game", 1609), XSTR("The currently active campaign cannot be found.  Please select another...", 1600));
1017 
1018 		switch (rc) {
1019 			case 0:
1020 				gameseq_post_event(GS_EVENT_CAMPAIGN_ROOM);
1021 				break;
1022 
1023 			case 1:
1024 				gameseq_post_event(GS_EVENT_INITIAL_PLAYER_SELECT);
1025 				break;
1026 
1027 			case 2:
1028 				main_hall_exit_game();
1029 				break;
1030 
1031 			default:
1032 				gameseq_post_event(GS_EVENT_CAMPAIGN_ROOM);
1033 				break;
1034 		}
1035 	}
1036 
1037 	// maybe run the player tips popup
1038 	player_tips_popup();
1039 
1040 	// if we were supposed to skip a frame, then stop doing it after 1 frame
1041 	if (Main_hall_frame_skip) {
1042 		Main_hall_frame_skip = 0;
1043 	}
1044 }
1045 
1046 /**
1047  * Close the main hall proper
1048  */
main_hall_close()1049 void main_hall_close()
1050 {
1051 	if (!Main_hall_inited) {
1052 		return;
1053 	}
1054 
1055 	// unload the main hall bitmap
1056 	if (Main_hall_bitmap != -1) {
1057 		bm_release(Main_hall_bitmap);
1058 	}
1059 
1060 	// unload any bitmaps
1061 	if (Main_hall_mask >= 0) {
1062 		// make sure we unlock the mask bitmap so it can be unloaded
1063 		bm_unlock(Main_hall_mask);
1064 		bm_release(Main_hall_mask);
1065 	}
1066 
1067 	// free up any (possibly) playing misc animation handles
1068 	for (auto &misc_anim : Main_hall_misc_anim) {
1069 		if (misc_anim.num_frames > 0) {
1070 			generic_anim_unload(&misc_anim);
1071 		}
1072 	}
1073 
1074 	// free up any (possibly) playing door animation handles
1075 	for (auto &door_anim : Main_hall_door_anim) {
1076 		if (door_anim.num_frames > 0) {
1077 			generic_anim_unload(&door_anim);
1078 		}
1079 	}
1080 
1081 	// stop any playing door sounds
1082 	for (auto &sound_pair : Main_hall_door_sound_handles) {
1083 		if ((sound_pair.second.isValid()) && snd_is_playing(sound_pair.second)) {
1084 			if (main_hall_can_truncate(sound_pair.first)) {        // don't cut off the glow sounds (requested by Dan)
1085 				snd_stop(sound_pair.second);
1086 				sound_pair.second = sound_handle::invalid();
1087 			}
1088 		}
1089 	}
1090 
1091 	// close any snazzy menu details
1092 	snazzy_menu_close();
1093 
1094 	// no fish
1095 	fishtank_stop();
1096 
1097 	// unpause
1098 	Main_hall_paused = 0;
1099 
1100 	// not inited anymore
1101 	Main_hall_inited = 0;
1102 }
1103 
1104 /**
1105  * Return music index
1106  *
1107  * @param main_hall_num Index to seek
1108  * @return Index into spooled music
1109  */
main_hall_get_music_index(int main_hall_num)1110 int main_hall_get_music_index(int main_hall_num)
1111 {
1112 	main_hall_defines *hall;
1113 	int index;
1114 
1115 	if (main_hall_num < 0) {
1116 		return -1;
1117 	}
1118 
1119 	hall = &Main_hall_defines.at(main_hall_num).at(main_hall_get_resolution_index(main_hall_num));
1120 
1121 	// Goober5000 - try substitute first
1122 	index = event_music_get_spooled_music_index(hall->substitute_music_name);
1123 	if ( (index >= 0) && (Spooled_music[index].flags & SMF_VALID) ) {
1124 		return index;
1125 	}
1126 
1127 	// now try regular
1128 	index = event_music_get_spooled_music_index(hall->music_name);
1129 	if ( (index >= 0) && (Spooled_music[index].flags & SMF_VALID) ) {
1130 		return index;
1131 	}
1132 
1133 	return -1;
1134 }
1135 
1136 /**
1137  * Start the main hall music playing
1138  */
main_hall_start_music()1139 void main_hall_start_music()
1140 {
1141 	char *filename;
1142 
1143 	// start a looping ambient sound
1144 	main_hall_start_ambient();
1145 
1146 	// if we have selected no music, then don't do this
1147 	if (Cmdline_freespace_no_music) {
1148 		return;
1149 	}
1150 
1151 	// already playing?
1152 	if (Main_hall_music_handle >= 0) {
1153 		return;
1154 	}
1155 
1156 	// get music
1157 	Main_hall_music_index = main_hall_get_music_index(main_hall_id());
1158 	if (Main_hall_music_index < 0) {
1159 		nprintf(("Warning", "No music file exists to play music at the main menu!\n"));
1160 		return;
1161 	}
1162 
1163 	filename = Spooled_music[Main_hall_music_index].filename;
1164 	Assert(filename != nullptr);
1165 
1166 	// get handle
1167 	Main_hall_music_handle = audiostream_open(filename, ASF_MENUMUSIC);
1168 	if (Main_hall_music_handle < 0) {
1169 		nprintf(("Warning", "No music file exists to play music at the main menu!\n"));
1170 		return;
1171 	}
1172 
1173 	audiostream_play(Main_hall_music_handle, Master_event_music_volume, 1);
1174 }
1175 
1176 /**
1177  * Stop the main hall music
1178  */
main_hall_stop_music(bool fade)1179 void main_hall_stop_music(bool fade)
1180 {
1181 	if (Main_hall_music_handle != -1) {
1182 		audiostream_close_file(Main_hall_music_handle, fade);
1183 		Main_hall_music_handle = -1;
1184 	}
1185 }
1186 
1187 /**
1188  * Render all playing misc animations
1189  *
1190  * @param frametime Animation frame time
1191  */
main_hall_render_misc_anims(float frametime,bool over_doors)1192 void main_hall_render_misc_anims(float frametime, bool over_doors)
1193 {
1194 	std::deque<bool> group_anims_weve_checked;
1195 	int idx, s_idx, jdx;
1196 
1197 	// render all misc animations
1198 	for (idx = 0; idx < Main_hall->num_misc_animations; idx++) {
1199 		// give it a spot in the vector
1200 		group_anims_weve_checked.push_back(false);
1201 
1202 		// render it
1203 		if (Main_hall_misc_anim.at(idx).num_frames > 0 && Main_hall->misc_anim_over_doors.at(idx) == over_doors) {
1204 			// animation is paused
1205 			if (Main_hall->misc_anim_paused.at(idx)) {
1206 				// if the timestamp is -1, then regenerate it
1207 				if (Main_hall->misc_anim_delay.at(idx).at(0) == -1) {
1208 					int regen_idx = -1;
1209 
1210 					// if this is part of a group, we should do additional checking
1211 					if (Main_hall->misc_anim_group.at(idx) >= 0) {
1212 
1213 						// make sure we haven't already checked it
1214 						if (group_anims_weve_checked.at(idx) == false) {
1215 							SCP_vector<int> group_indexes; //stores indexes of which anims are part of a group
1216 							bool all_neg1 = true;
1217 
1218 							// okay... now we need to make sure all anims in this group are paused and -1
1219 							for (jdx = 0; jdx < Main_hall->num_misc_animations; jdx++) {
1220 								if (Main_hall->misc_anim_group.at(jdx) == Main_hall->misc_anim_group.at(idx)) {
1221 									Assert(group_anims_weve_checked.size() < INT_MAX);
1222 									if ((int)group_anims_weve_checked.size() <= jdx) {
1223 										group_anims_weve_checked.push_back(true);
1224 									} else {
1225 										group_anims_weve_checked.at(jdx) = true;
1226 									}
1227 
1228 									group_indexes.push_back(jdx);
1229 
1230 									if (!Main_hall->misc_anim_paused.at(jdx) || Main_hall->misc_anim_delay.at(jdx).at(0) != -1) {
1231 										all_neg1 = false;
1232 									}
1233 								}
1234 							}
1235 
1236 							// if the entire group is paused and off, pick a random one to regenerate
1237 							if (all_neg1) {
1238 								Assert(group_indexes.size() < INT_MAX);
1239 								regen_idx = group_indexes[Random::next((int)group_indexes.size())];
1240 							}
1241 						}
1242 					} else { // not part of a group, so just handle this index
1243 						regen_idx = idx;
1244 					}
1245 
1246 					// reset it to some random value (based on MIN and MAX) and continue
1247 					if (regen_idx >= 0) {
1248 						int min = Main_hall->misc_anim_delay.at(regen_idx).at(1);
1249 						int max = Main_hall->misc_anim_delay.at(regen_idx).at(2);
1250 						Main_hall->misc_anim_delay.at(regen_idx).at(0) = timestamp(min + (int) (frand() * (max - min)));
1251 					}
1252 
1253 				// if the timestamp is not -1 and has popped, play the anim and make the timestamp -1
1254 				} else if (timestamp_elapsed(Main_hall->misc_anim_delay.at(idx).at(0))) {
1255 					Main_hall->misc_anim_paused.at(idx) = false;
1256 					generic_anim_reset(&Main_hall_misc_anim.at(idx));
1257 
1258 					// kill the timestamp
1259 					Main_hall->misc_anim_delay.at(idx).at(0) = -1;
1260 
1261 					// reset the "should be playing" flags
1262 					Assert(Main_hall->misc_anim_sound_flag.at(idx).size() < INT_MAX);
1263 					for (s_idx = 0; s_idx < (int)Main_hall->misc_anim_sound_flag.at(idx).size(); s_idx++) {
1264 						Main_hall->misc_anim_sound_flag.at(idx).at(s_idx) = 0;
1265 					}
1266 				}
1267 			} else { // animation is not paused
1268 				Assert(Main_hall->misc_anim_special_sounds.at(idx).size() < INT_MAX);
1269 				for (s_idx = 0; s_idx < (int)Main_hall->misc_anim_special_sounds.at(idx).size(); s_idx++) {
1270 					// if we've passed the trigger point, then play the sound and break out of the loop
1271 					if ( (Main_hall_misc_anim.at(idx).current_frame >= Main_hall->misc_anim_special_trigger.at(idx).at(s_idx))
1272 							&& !Main_hall->misc_anim_sound_flag.at(idx).at(s_idx) ) {
1273 						Main_hall->misc_anim_sound_flag.at(idx).at(s_idx) = 1;
1274 
1275 						auto sound = Main_hall->misc_anim_special_sounds.at(idx).at(s_idx);
1276 
1277 						// Check if the sound is valid
1278 						if (sound.isValid())
1279 						{
1280 							// play the sound
1281 							auto snd = gamesnd_get_interface_sound(sound);
1282 							auto handle = snd_play(snd, Main_hall->misc_anim_sound_pan.at(idx));
1283 							Main_hall_misc_sound_handles.emplace_back(snd, handle);
1284 						}
1285 						break;
1286 					}
1287 				}
1288 
1289 				// animation has reached the last frame
1290 				if (Main_hall_misc_anim.at(idx).current_frame == Main_hall_misc_anim.at(idx).num_frames - 1) {
1291 					Main_hall->misc_anim_delay.at(idx).at(0) = -1;
1292 
1293 					// this helps the above code reset the timers
1294 					// MISC_ANIM_MODE_HOLD simply stops on the last frame, so we don't care
1295 					// MISC_ANIM_MODE_LOOPED just loops so we don't care either
1296 					if (Main_hall->misc_anim_modes.at(idx) == MISC_ANIM_MODE_TIMED) {
1297 						Main_hall->misc_anim_paused.at(idx) = true;
1298 					}
1299 					// don't reset sound for MISC_ANIM_MODE_HOLD
1300 					if (Main_hall->misc_anim_modes.at(idx) != MISC_ANIM_MODE_HOLD) {
1301 						// reset the "should be playing" flags
1302 						Assert(Main_hall->misc_anim_sound_flag.at(idx).size() < INT_MAX);
1303 						for (s_idx = 0; s_idx < (int)Main_hall->misc_anim_sound_flag.at(idx).size(); s_idx++) {
1304 							Main_hall->misc_anim_sound_flag.at(idx).at(s_idx) = 0;
1305 						}
1306 					}
1307 				}
1308 
1309 				// actually render it
1310 				if (Main_hall_frame_skip || Main_hall_paused) {
1311 					frametime = 0;
1312 				}
1313 				generic_anim_render(&Main_hall_misc_anim.at(idx), frametime, Main_hall->misc_anim_coords.at(idx).at(0), Main_hall->misc_anim_coords.at(idx).at(1), true);
1314 			}
1315 		}
1316 	}
1317 }
1318 
1319 /**
1320  * Render all playing door animations
1321  * @param frametime Animation frame time
1322  */
main_hall_render_door_anims(float frametime)1323 void main_hall_render_door_anims(float frametime)
1324 {
1325 	int idx;
1326 
1327 	// render all door animations
1328 	Assert(Main_hall_door_anim.size() < INT_MAX);
1329 	for (idx = 0; idx < (int)Main_hall_door_anim.size(); idx++) {
1330 		if (Main_hall_door_anim.at(idx).num_frames > 0) {
1331 		// first pair : coords of where to play a given door anim
1332 		// second pair : center of a given door anim in windowed mode
1333 			generic_anim_render(&Main_hall_door_anim.at(idx), frametime, Main_hall->door_anim_coords.at(idx).at(0), Main_hall->door_anim_coords.at(idx).at(1), true);
1334 		}
1335 	}
1336 }
1337 
1338 /**
1339  * Any necessary processing based upon the mouse location
1340  * @param cur_region Region of current mouse location
1341  */
main_hall_handle_mouse_location(int cur_region)1342 void main_hall_handle_mouse_location(int cur_region)
1343 {
1344 	if (Main_hall_frame_skip) {
1345 		return;
1346 	}
1347 
1348 	if (cur_region >= (int) Main_hall->regions.size()) {
1349 		// MWA -- inserted return since Int3() was tripped when hitting L from main menu.
1350 		return;
1351 	}
1352 
1353 	// if the mouse is now over a resgion
1354 	if (cur_region != -1) {
1355 		// if we're still over the same region we were last frame, check stuff
1356 		if (cur_region == Main_hall_mouse_region) {
1357 			// if we have a linger timestamp set and it has expired, then get moving
1358 			if ((Main_hall_region_linger_stamp != -1) && timestamp_elapsed(Main_hall_region_linger_stamp)) {
1359 				main_hall_mouse_grab_region(cur_region);
1360 
1361 				// release the region linger stamp
1362 				Main_hall_region_linger_stamp = -1;
1363 			}
1364 		} else {
1365 			// if we're currently on another region, release it
1366 			if ( (Main_hall_mouse_region != -1) && (cur_region != Main_hall_mouse_region) ) {
1367 				main_hall_mouse_release_region(Main_hall_mouse_region);
1368 			}
1369 
1370 			// set the linger time
1371 			if (Main_hall_region_linger_stamp == -1) {
1372 				Main_hall_mouse_region = cur_region;
1373 				Main_hall_region_linger_stamp = timestamp(MAIN_HALL_REGION_LINGER);
1374 			}
1375 		}
1376 	} else { // if it was over a region but isn't anymore, release that region
1377 		if (Main_hall_mouse_region != -1) {
1378 			main_hall_mouse_release_region(Main_hall_mouse_region);
1379 			Main_hall_mouse_region = -1;
1380 
1381 			// release the region linger timestamp
1382 			Main_hall_region_linger_stamp = -1;
1383 		}
1384 	}
1385 }
1386 
1387 /**
1388  * If the mouse has moved off of the currently active region, handle the anim accordingly
1389  * @param region Region of prior mouse location
1390  */
main_hall_mouse_release_region(int region)1391 void main_hall_mouse_release_region(int region)
1392 {
1393 	if (Main_hall_frame_skip) {
1394 		return;
1395 	}
1396 	// don't do anything if there are no animations to play
1397 	else if (region >= (int) Main_hall_door_anim.size()) {
1398 		return;
1399 	}
1400 
1401 	// run backwards and stop at the first frame
1402 	Main_hall_door_anim.at(region).direction = GENERIC_ANIM_DIRECTION_BACKWARDS | GENERIC_ANIM_DIRECTION_NOLOOP;
1403 
1404 	// check for door sounds, ignoring the OPTIONS_REGION (which isn't a door)
1405 	if (Main_hall_door_anim.at(region).num_frames > 0) {
1406 		auto sound_pair = &Main_hall_door_sound_handles.at(region);
1407 
1408 		// don't stop the toaster oven or microwave regions from playing all the way through
1409 		if (sound_pair->second.isValid()) {
1410 			snd_stop(sound_pair->second);
1411 		}
1412 
1413 		auto sound = Main_hall->door_sounds.at(region).at(1);
1414 
1415 		if (sound.isValid())
1416 		{
1417 			sound_pair->first = gamesnd_get_interface_sound(sound);
1418 			sound_pair->second = snd_play(sound_pair->first, Main_hall->door_sound_pan.at(region));
1419 		}
1420 
1421 		// start the sound playing at the right spot relative to the completion of the animation
1422 		if ( Main_hall_door_anim.at(region).current_frame != -1 ) {
1423 			snd_set_pos(sound_pair->second,
1424 						(float) ((Main_hall_door_anim.at(region).keyframe) ? Main_hall_door_anim.at(region).keyframe :
1425 								 Main_hall_door_anim.at(region).num_frames - Main_hall_door_anim.at(region).current_frame)
1426 							/ (float) Main_hall_door_anim.at(region).num_frames,
1427 						1);
1428 		}
1429 	}
1430 }
1431 
1432 /**
1433  * If the mouse has moved on this region, handle it accordingly
1434  * @param region Region of current mouse location
1435  */
main_hall_mouse_grab_region(int region)1436 void main_hall_mouse_grab_region(int region)
1437 {
1438 	if (Main_hall_frame_skip) {
1439 		return;
1440 	}
1441 	// don't do anything if there are no animations to play
1442 	else if (region >= (int) Main_hall_door_anim.size()) {
1443 		return;
1444 	}
1445 
1446 	// run forwards
1447 	Main_hall_door_anim.at(region).direction = GENERIC_ANIM_DIRECTION_FORWARDS;
1448 	// stay on last frame if we have no keyframe
1449 	if (!Main_hall_door_anim.at(region).keyframe) {
1450 		Main_hall_door_anim.at(region).direction += GENERIC_ANIM_DIRECTION_NOLOOP;
1451 	}
1452 
1453 	// check for opening/starting sounds
1454 	// kill the currently playing sounds if necessary
1455 	auto sound_pair = &Main_hall_door_sound_handles.at(region);
1456 	if (sound_pair->second.isValid()) {
1457 		snd_stop(sound_pair->second);
1458 	}
1459 
1460 
1461 	auto sound = Main_hall->door_sounds.at(region).at(0);
1462 
1463 	if (sound.isValid())
1464 	{
1465 		sound_pair->first = gamesnd_get_interface_sound(sound);
1466 		sound_pair->second = snd_play(sound_pair->first, Main_hall->door_sound_pan.at(region));
1467 	}
1468 
1469 	// start the sound playing at the right spot relative to the completion of the animation
1470 	if ( (Main_hall_door_anim.at(region).num_frames > 0) && (Main_hall_door_anim.at(region).current_frame != -1) ) {
1471 		snd_set_pos(sound_pair->second,
1472 					(float) Main_hall_door_anim.at(region).current_frame
1473 						/ (float) Main_hall_door_anim.at(region).num_frames,
1474 					1);
1475 	}
1476 }
1477 
1478 /**
1479  * Handle any right clicks which may have occured
1480  */
main_hall_handle_right_clicks()1481 void main_hall_handle_right_clicks()
1482 {
1483 	int new_region;
1484 
1485 	if (Main_hall_frame_skip) {
1486 		return;
1487 	}
1488 
1489 	// check to see if the button has been clicked
1490 	if (!Main_hall_right_click) {
1491 		if (mouse_down(MOUSE_RIGHT_BUTTON)) {
1492 			// cycle through the available regions
1493 			if (Main_hall_last_clicked_region == (int) Main_hall_door_anim.size() - 1) {
1494 				new_region = 0;
1495 			} else {
1496 				new_region = Main_hall_last_clicked_region + 1;
1497 			}
1498 
1499 			// set the position of the mouse cursor and the newly clicked region
1500 			int mx = Main_hall->door_anim_coords.at(new_region).at(2);
1501 			int my = Main_hall->door_anim_coords.at(new_region).at(3);
1502 			gr_resize_screen_pos( &mx, &my, nullptr, nullptr, GR_RESIZE_MENU );
1503 
1504 			if (mx < 0) {
1505 				mx = 0;
1506 			}
1507 			if (mx >= gr_screen.max_w) {
1508 				mx = gr_screen.max_w - 1;
1509 			}
1510 			if (my < 0) {
1511 				my = 0;
1512 			}
1513 			if (my >= gr_screen.max_h) {
1514 				my = gr_screen.max_h - 1;
1515 			}
1516 
1517 			mouse_set_pos( mx, my );
1518 
1519 			main_hall_handle_mouse_location(new_region);
1520 			Main_hall_last_clicked_region = new_region;
1521 
1522 			// set the mouse as being clicked
1523 			Main_hall_right_click = 1;
1524 		}
1525 	// set the mouse as being unclicked
1526 	} else if (Main_hall_right_click && !(mouse_down(MOUSE_RIGHT_BUTTON))) {
1527 		Main_hall_right_click = 0;
1528 	}
1529 }
1530 
1531 /**
1532  * Cull any door sounds that have finished playing
1533  */
main_hall_cull_door_sounds()1534 void main_hall_cull_door_sounds()
1535 {
1536 	// basically just set the handle of any finished sound to be -1, so that we know its free anywhere else in the code we may need it
1537 	for (auto &sound_pair : Main_hall_door_sound_handles) {
1538 		if (sound_pair.second.isValid() && !snd_is_playing(sound_pair.second)) {
1539 			sound_pair.second = sound_handle::invalid();
1540 		}
1541 	}
1542 
1543 	// for misc sounds, we just remove the handle from the list if it's done
1544 	Main_hall_misc_sound_handles.remove_if([](const std::pair<game_snd*, sound_handle> &sound_pair)->bool {
1545 		return !snd_is_playing(sound_pair.second);
1546 	});
1547 }
1548 
1549 /**
1550  * Insert random intercom sounds
1551  */
main_hall_handle_random_intercom_sounds()1552 void main_hall_handle_random_intercom_sounds()
1553 {
1554 	if (Main_hall->num_random_intercom_sounds <= 0)
1555 	{
1556 		// If there are no intercom sounds then just skip this section
1557 		return;
1558 	}
1559 
1560 	// if we have no timestamp for the next random sound, then set one
1561 	if ((Main_hall_next_intercom_sound_stamp == -1) && (!Main_hall_intercom_sound_handle.isValid())) {
1562 		int delta_ms = Random::next(Main_hall->intercom_delay.at(Main_hall_next_intercom_sound).at(0), Main_hall->intercom_delay.at(Main_hall_next_intercom_sound).at(1));
1563 		Main_hall_next_intercom_sound_stamp = timestamp(delta_ms);
1564 	}
1565 
1566 	// if the there is no sound playing
1567 	if (!Main_hall_intercom_sound_handle.isValid()) {
1568 		if (Main_hall_paused) {
1569 			return;
1570 		}
1571 
1572 		// if the timestamp has popped, play a sound
1573 		if ( (Main_hall_next_intercom_sound_stamp != -1) && (timestamp_elapsed(Main_hall_next_intercom_sound_stamp)) ) {
1574 			auto sound = Main_hall->intercom_sounds.at(Main_hall_next_intercom_sound);
1575 
1576 			// Check if the sound is valid
1577 			if (sound.isValid())
1578 			{
1579 				// play the sound
1580 				Main_hall_intercom_sound_handle = snd_play(gamesnd_get_interface_sound(sound));
1581 
1582 				// unset the timestamp
1583 				Main_hall_next_intercom_sound_stamp = -1;
1584 			}
1585 
1586 			// unset the timestamp
1587 			Main_hall_next_intercom_sound_stamp = -1;
1588 		}
1589 	} else { // if the sound is playing
1590 		// if the sound has finished, set the timestamp and continue
1591 		if (!snd_is_playing(Main_hall_intercom_sound_handle)) {
1592 			// increment the next sound
1593 			if (Main_hall_next_intercom_sound >= (Main_hall->num_random_intercom_sounds-1)) {
1594 				Main_hall_next_intercom_sound = 0;
1595 			} else {
1596 				Main_hall_next_intercom_sound++;
1597 			}
1598 
1599 			// set the timestamp
1600 			int delta_ms = Random::next(Main_hall->intercom_delay.at(Main_hall_next_intercom_sound).at(0), Main_hall->intercom_delay.at(Main_hall_next_intercom_sound).at(1));
1601 			Main_hall_next_intercom_sound_stamp = timestamp(delta_ms);
1602 
1603 			// release the sound handle
1604 			Main_hall_intercom_sound_handle = sound_handle::invalid();
1605 		}
1606 	}
1607 }
1608 
1609 /**
1610  * Set the notification string with its decay timeout
1611  * @param str Notification string
1612  */
main_hall_set_notify_string(const char * str)1613 void main_hall_set_notify_string(const char *str)
1614 {
1615 	strcpy_s(Main_hall_notify_text,str);
1616 	Main_hall_notify_stamp = timestamp(MAIN_HALL_NOTIFY_TIME);
1617 }
1618 
1619 /**
1620  * Handle any drawing, culling, etc of notification messages
1621  */
main_hall_notify_do()1622 void main_hall_notify_do()
1623 {
1624 	// check to see if we should try and do something
1625 	if (Main_hall_notify_stamp != -1) {
1626 		// if the text time has expired
1627 		if (timestamp_elapsed(Main_hall_notify_stamp)) {
1628 			strcpy_s(Main_hall_notify_text,"");
1629 			Main_hall_notify_stamp = -1;
1630 		} else {
1631 			int w,h;
1632 
1633 			int old_font = font::get_current_fontnum();
1634 
1635 			gr_set_color_fast(&Color_bright);
1636 			font::set_font(Main_hall->font);
1637 
1638 			gr_get_string_size(&w,&h,Main_hall_notify_text);
1639 			gr_printf_menu_zoomed((gr_screen.max_w_unscaled_zoomed - w)/2, gr_screen.max_h_unscaled_zoomed - (h * 4 + 4), "%s", Main_hall_notify_text);
1640 
1641 			font::set_font(old_font);
1642 		}
1643 	}
1644 }
1645 
1646 /**
1647  * Start a looping ambient sound for main hall
1648  */
main_hall_start_ambient()1649 void main_hall_start_ambient()
1650 {
1651 	int play_ambient_loop = 0;
1652 
1653 	if (Main_hall_paused) {
1654 		return;
1655 	}
1656 
1657 	if (!Main_hall_ambient_loop.isValid()) {
1658 		play_ambient_loop = 1;
1659 	} else {
1660 		if (!snd_is_playing(Main_hall_ambient_loop)) {
1661 			play_ambient_loop = 1;
1662 		}
1663 	}
1664 
1665 	if (play_ambient_loop) {
1666 		Main_hall_ambient_loop = snd_play_looping(gamesnd_get_interface_sound(InterfaceSounds::MAIN_HALL_AMBIENT));
1667 	}
1668 
1669 	// no need to restart the intercom, since the game will set the timestamp for a new one
1670 }
1671 
1672 /**
1673  * Stop a looping ambient sound for the main hall
1674  */
main_hall_stop_ambient()1675 void main_hall_stop_ambient()
1676 {
1677 	if (Main_hall_ambient_loop.isValid()) {
1678 		snd_stop(Main_hall_ambient_loop);
1679 		Main_hall_ambient_loop = sound_handle::invalid();
1680 	}
1681 
1682 	// also stop any PA announcements
1683 	if (Main_hall_intercom_sound_handle.isValid()) {
1684 		snd_stop(Main_hall_intercom_sound_handle);
1685 		Main_hall_intercom_sound_handle = sound_handle::invalid();
1686 	}
1687 
1688 	// stop any playing door sounds
1689 	for (auto &sound_pair : Main_hall_door_sound_handles) {
1690 		if ((sound_pair.second.isValid()) && snd_is_playing(sound_pair.second)) {
1691 			if (main_hall_can_truncate(sound_pair.first)) {        // don't cut off the glow sounds (requested by Dan)
1692 				snd_stop(sound_pair.second);
1693 				sound_pair.second = sound_handle::invalid();
1694 			}
1695 		}
1696 	}
1697 
1698 	// also stop any misc sounds
1699 	for (auto &sound_pair : Main_hall_misc_sound_handles) {
1700 		if (sound_pair.second.isValid() && snd_is_playing(sound_pair.second)) {
1701 			if (main_hall_can_truncate(sound_pair.first)) {
1702 				snd_stop(sound_pair.second);
1703 				sound_pair.second = sound_handle::invalid();
1704 			}
1705 		}
1706 	}
1707 }
1708 
1709 /**
1710  * Reset the volume of the looping ambient sound.
1711  *
1712  * @note This is called from the options screen when the looping ambient sound might be playing.
1713  */
main_hall_reset_ambient_vol()1714 void main_hall_reset_ambient_vol()
1715 {
1716 	if (Main_hall_ambient_loop.isValid()) {
1717 		snd_set_volume(Main_hall_ambient_loop, gamesnd_get_interface_sound(InterfaceSounds::MAIN_HALL_AMBIENT)->volume_range.next());
1718 	}
1719 }
1720 
1721 /**
1722  * Blit the freespace version number
1723  */
main_hall_blit_version()1724 void main_hall_blit_version()
1725 {
1726 	int w, h;
1727 
1728 	// format the version string
1729 	auto version_string = gameversion::get_version_string();
1730 
1731 	int old_font = font::get_current_fontnum();
1732 	font::set_font(Main_hall->font);
1733 
1734 	// get the length of the string
1735 	gr_get_string_size(&w, &h, version_string.c_str());
1736 
1737 	// print the string near the lower left corner
1738 	gr_set_color_fast(&Color_bright_white);
1739 	gr_string(5, gr_screen.max_h_unscaled_zoomed - (h * 2 + 6), version_string.c_str(), GR_RESIZE_MENU_ZOOMED);
1740 
1741 	font::set_font(old_font);
1742 }
1743 
1744 /**
1745  * Blit any necessary tooltips
1746  */
main_hall_maybe_blit_tooltips()1747 void main_hall_maybe_blit_tooltips()
1748 {
1749 	int w, h;
1750 
1751 	// if we're over no region - don't blit anything
1752 	if (Main_hall_mouse_region < 0) {
1753 		return;
1754 	}
1755 
1756 	if (Main_hall_mouse_region >= (int) Main_hall->regions.size()) {
1757 		Error(LOCATION, "Missing region description for index %d!\n", Main_hall_mouse_region);
1758 	}
1759 
1760 	// set the color and blit the string
1761 	if (!help_overlay_active(Main_hall_overlay_id)) {
1762 		const char* desc = Main_hall->regions[Main_hall_mouse_region].description.c_str();
1763 
1764 		int old_font = font::get_current_fontnum();
1765 		font::set_font(Main_hall->font);
1766 		// get the width of the string
1767 		gr_get_string_size(&w, &h, desc);
1768 		int text_y;
1769 		if (Main_hall->region_yval == -1) {
1770 			text_y = gr_screen.max_h_unscaled - ((gr_screen.max_h_unscaled - gr_screen.max_h_unscaled_zoomed) / 2) - Main_hall->tooltip_padding - h;
1771 		} else {
1772 			text_y = Main_hall->region_yval;
1773 		}
1774 		int shader_y = text_y - (Main_hall->tooltip_padding);	// subtract more to pull higher
1775 
1776 		gr_set_shader(&Main_hall_tooltip_shader);
1777 		gr_shade(0, shader_y, gr_screen.max_w_unscaled, (gr_screen.max_h_unscaled - shader_y), GR_RESIZE_MENU);
1778 
1779 		gr_set_color_fast(&Color_bright_white);
1780 		gr_string((gr_screen.max_w_unscaled - w)/2, text_y, desc, GR_RESIZE_MENU);
1781 
1782 		font::set_font(old_font);
1783 	}
1784 }
1785 
1786 /**
1787  * Process help messages
1788  */
main_hall_process_help_stuff()1789 void main_hall_process_help_stuff()
1790 {
1791 	int w, h;
1792 	char str[255];
1793 
1794 	// if the timestamp has popped, don't do anything
1795 	if (Main_hall_help_stamp == -1) {
1796 		return;
1797 	}
1798 
1799 	// if the timestamp has popped, advance frame
1800 	if (timestamp_elapsed(Main_hall_help_stamp)) {
1801 		Main_hall_f1_text_frame++;
1802 	}
1803 
1804 	int old_font = font::get_current_fontnum();
1805 	font::set_font(Main_hall->font);
1806 
1807 	// otherwise print out the message
1808 	strcpy_s(str, XSTR( "Press F1 for help", 371));
1809 	gr_get_string_size(&w, &h, str);
1810 
1811 	int y_anim_offset = Main_hall_f1_text_frame;
1812 
1813 	// if anim is off the screen finally, stop altogether
1814 	if ( (y_anim_offset >= (2*Main_hall->tooltip_padding) + h) || (help_overlay_active(Main_hall_overlay_id)) ) {
1815 		Main_hall_f1_text_frame = -1;
1816 		Main_hall_help_stamp = -1;
1817 		F1_text_done = 1;
1818 		return;
1819 	}
1820 
1821 	// set the color and print out text and shader
1822 	gr_set_color_fast(&Color_bright_white);
1823 	gr_set_shader(&Main_hall_tooltip_shader);
1824 	gr_shade(0, 0, gr_screen.max_w_unscaled_zoomed, (2*Main_hall->tooltip_padding) + h - y_anim_offset, GR_RESIZE_MENU_ZOOMED);
1825 	gr_string((gr_screen.max_w_unscaled_zoomed - w)/2, Main_hall->tooltip_padding - y_anim_offset, str, GR_RESIZE_MENU_ZOOMED);
1826 
1827 	font::set_font(old_font);
1828 }
1829 
1830 /**
1831  * CommanderDJ - finds the mainhall struct whose name is equal to the passed string
1832  * @param name_to_find Name of mainhall we're searching for
1833  *
1834  * \return pointer to mainhall if one with a matching name is found
1835  * \return NULL otherwise
1836  */
main_hall_get_pointer(const SCP_string & name_to_find)1837 main_hall_defines* main_hall_get_pointer(const SCP_string &name_to_find)
1838 {
1839 	unsigned int i;
1840 
1841 	for (i = 0; i < Main_hall_defines.size(); i++) {
1842 		if (Main_hall_defines.at(i).at(0).name == name_to_find) {
1843 			return &Main_hall_defines.at(i).at(main_hall_get_resolution_index(i));
1844 		}
1845 	}
1846 	return nullptr;
1847 }
1848 
1849 /**
1850  * CommanderDJ - finds the mainhall struct whose name is equal to the passed string
1851  * @param name_to_find Name of mainhall we're searching for
1852  *
1853  * \return index of mainhall in Main_hall_defines if one with a matching name is found
1854  * \return -1 otherwise
1855  */
1856 
main_hall_get_index(const SCP_string & name_to_find)1857 int main_hall_get_index(const SCP_string &name_to_find)
1858 {
1859 	unsigned int i;
1860 
1861 	for (i = 0; i < Main_hall_defines.size(); i++) {
1862 		if (Main_hall_defines.at(i).at(0).name == name_to_find) {
1863 			return i;
1864 		}
1865 	}
1866 	return -1;
1867 }
1868 
main_hall_get_resolution_index(int main_hall_num)1869 int main_hall_get_resolution_index(int main_hall_num)
1870 {
1871 	size_t i;
1872 	float aspect_ratio = (float)gr_screen.center_w / (float)gr_screen.center_h;
1873 
1874 	for (i = Main_hall_defines.at(main_hall_num).size() - 1; i >= 1; i--) {
1875 		main_hall_defines* m = &Main_hall_defines.at(main_hall_num).at(i);
1876 		if (gr_screen.center_w >= m->min_width && gr_screen.center_h >= m->min_height && aspect_ratio >= m->min_aspect_ratio) {
1877 			return (int)i;
1878 		}
1879 	}
1880 	return 0;
1881 }
1882 
main_hall_get_name(SCP_string & name,unsigned int index)1883 void main_hall_get_name(SCP_string &name, unsigned int index)
1884 {
1885 	if (index>=Main_hall_defines.size()) {
1886 		name = "";
1887 	} else {
1888 		name = Main_hall_defines.at(index).at(0).name;
1889 	}
1890 }
1891 
main_hall_get_overlay_id()1892 int main_hall_get_overlay_id()
1893 {
1894 	if (Main_hall==nullptr) {
1895 		return -1;
1896 	} else {
1897 		return Main_hall_overlay_id;
1898 	}
1899 }
1900 
main_hall_get_overlay_resolution_index()1901 int main_hall_get_overlay_resolution_index()
1902 {
1903 	if (Main_hall==nullptr) {
1904 		return -1;
1905 	} else {
1906 		return Main_hall->help_overlay_resolution_index;
1907 	}
1908 }
1909 
1910 // what main hall we're on
main_hall_id()1911 int main_hall_id()
1912 {
1913 	if (Main_hall==nullptr) {
1914 		return -1;
1915 	} else {
1916 		return main_hall_get_index(Main_hall->name);
1917 	}
1918 }
1919 
1920 // CommanderDJ - helper function for initialising intercom sounds vectors based on number of sounds
1921 // To be called after num_intercom_sounds has been parsed
intercom_sounds_init(main_hall_defines & m,bool first_time,int base_num)1922 void intercom_sounds_init(main_hall_defines &m, bool first_time, int base_num)
1923 {
1924 	if (Cmdline_reparse_mainhall && first_time) {
1925 		// we could be reparsing with a different number of intercom sounds, so clear these and reinitialise
1926 		m.intercom_delay.clear();
1927 		m.intercom_sounds.clear();
1928 		m.intercom_sound_pan.clear();
1929 	}
1930 
1931 	for (int idx = base_num; idx < m.num_random_intercom_sounds; idx++) {
1932 		// intercom_delay
1933 		m.intercom_delay.emplace_back();
1934 
1935 		// each delay has a min and a max
1936 		m.intercom_delay.back().push_back(0);
1937 		m.intercom_delay.back().push_back(0);
1938 
1939 		// intercom_sounds
1940 		m.intercom_sounds.push_back(interface_snd_id());
1941 
1942 		// intercom_sound_pan
1943 		m.intercom_sound_pan.push_back(0);
1944 	}
1945 }
1946 
1947 /**
1948  * Iinitialising misc anim vectors based on number of anims
1949  * @note To be called after num_misc_animations has been parsed
1950  *
1951  * @param m Main hall defines
1952  */
misc_anim_init(main_hall_defines & m,bool first_time,int base_num)1953 void misc_anim_init(main_hall_defines &m, bool first_time, int base_num)
1954 {
1955 	if (Cmdline_reparse_mainhall && first_time) {
1956 		// we could be reparsing with a different number of misc anims, so clear these and reinitialise
1957 		m.misc_anim_name.clear();
1958 		m.misc_anim_delay.clear();
1959 		m.misc_anim_paused.clear();
1960 		m.misc_anim_group.clear();
1961 		m.misc_anim_coords.clear();
1962 		m.misc_anim_modes.clear();
1963 		m.misc_anim_sound_pan.clear();
1964 		m.misc_anim_special_sounds.clear();
1965 		m.misc_anim_special_trigger.clear();
1966 		m.misc_anim_sound_flag.clear();
1967 	}
1968 
1969 	for (int idx = base_num; idx < m.num_misc_animations; idx++) {
1970 
1971 		// misc_anim_name
1972 		m.misc_anim_name.emplace_back();
1973 
1974 		// misc_anim_delay
1975 		m.misc_anim_delay.emplace_back();
1976 
1977 		// -1 default for the first entry, 0 for the others
1978 		m.misc_anim_delay.back().push_back(-1);
1979 		m.misc_anim_delay.back().push_back(0);
1980 		m.misc_anim_delay.back().push_back(0);
1981 
1982 		// misc_anim_paused
1983 		m.misc_anim_paused.push_back(1); // default is paused
1984 
1985 		// misc_anim_group
1986 		m.misc_anim_group.push_back(-1);
1987 
1988 		// misc_anim_coords
1989 		m.misc_anim_coords.emplace_back();
1990 
1991 		m.misc_anim_coords.back().push_back(0);
1992 		m.misc_anim_coords.back().push_back(0);
1993 
1994 		// misc_anim_modes
1995 		m.misc_anim_modes.push_back(MISC_ANIM_MODE_LOOP);
1996 
1997 		// misc_anim_sound_pan
1998 		m.misc_anim_sound_pan.push_back(0.0f);
1999 
2000 		// misc_anim_special_sounds
2001 		// parse_sound_list deals with the rest of the initialisation for this one
2002 		m.misc_anim_special_sounds.push_back(SCP_vector<interface_snd_id>());
2003 
2004 		// misc_anim_special_trigger
2005 		m.misc_anim_special_trigger.emplace_back();
2006 
2007 		m.misc_anim_special_trigger.back().push_back(0);
2008 
2009 		// misc_anim_sound_flag
2010 		m.misc_anim_sound_flag.emplace_back();
2011 	}
2012 }
2013 
2014 /**
2015  * Initialising door anim vectors based on number of anims
2016  * @note To be called after num_door_animations has been parsed
2017  *
2018  * @param m Main hall defines
2019  */
door_anim_init(main_hall_defines & m,bool first_time,int base_num)2020 void door_anim_init(main_hall_defines &m, bool first_time, int base_num)
2021 {
2022 	if (Cmdline_reparse_mainhall && first_time) {
2023 		/* since we could be reparsing with a different number of door
2024 		 anims, clear these and reinitialise. */
2025 		m.door_anim_name.clear();
2026 		m.door_anim_coords.clear();
2027 		m.door_sounds.clear();
2028 		m.door_sound_pan.clear();
2029 	}
2030 
2031 	for (int idx = base_num; idx < m.num_door_animations; idx++) {
2032 		// door_anim_name
2033 		m.door_anim_name.emplace_back();
2034 
2035 		// door_anim_coords
2036 		m.door_anim_coords.emplace_back();
2037 
2038 		// we want two pairs of coordinates for each animation
2039 		m.door_anim_coords.back().push_back(0);
2040 		m.door_anim_coords.back().push_back(0);
2041 		m.door_anim_coords.back().push_back(0);
2042 		m.door_anim_coords.back().push_back(0);
2043 
2044 		// door_sounds
2045 		m.door_sounds.emplace_back();
2046 
2047 		// door_sound_pan
2048 		m.door_sound_pan.push_back(0.0f);
2049 	}
2050 }
2051 
region_info_init(main_hall_defines & m)2052 void region_info_init(main_hall_defines &m)
2053 {
2054 	if (Cmdline_reparse_mainhall) {
2055 		m.regions.clear();
2056 	}
2057 
2058 	main_hall_region defaults[] = {
2059 		main_hall_region(0,  0,  XSTR( "Exit FreeSpace 2", 353), EXIT_REGION, ""),
2060 		main_hall_region(1, 'B', XSTR( "Barracks - Manage your FreeSpace 2 pilots", 354), BARRACKS_REGION, ""),
2061 		main_hall_region(2, 'R', XSTR( "Ready room - Start or continue a campaign", 355), START_REGION, ""),
2062 		main_hall_region(3, 'T', XSTR( "Tech room - View specifications of FreeSpace 2 ships and weaponry", 356), TECH_ROOM_REGION, ""),
2063 		main_hall_region(4,  0,  XSTR( "Options - Change your FreeSpace 2 options", 357), OPTIONS_REGION, ""),
2064 		main_hall_region(5, 'C', XSTR( "Campaign Room - View all available campaigns", 358), CAMPAIGN_ROOM_REGION, ""),
2065 		main_hall_region(6, 'G', "Quick start", QUICK_START_REGION, "")
2066 	};
2067 
2068 	for (int idx = 0; idx < 7; idx++) {
2069 		m.regions.push_back(defaults[idx]);
2070 	}
2071 
2072 	// XSTR( "Multiplayer - Start or join a multiplayer game", 359)
2073 	m.default_readyroom = true;
2074 }
2075 
2076 /**
2077  * Read in main hall table
2078  */
main_hall_table_init()2079 void main_hall_table_init()
2080 {
2081 	// clear the main hall entries
2082 	Main_hall_defines.clear();
2083 	Main_hall_sounds_to_not_truncate.clear();
2084 
2085 	// if mainhall.tbl exists, parse it
2086 	if (cf_exists_full("mainhall.tbl", CF_TYPE_TABLES)) {
2087 		parse_main_hall_table("mainhall.tbl");
2088 	}
2089 
2090 	// parse any modular tables
2091 	parse_modular_table("*-hall.tbm", parse_main_hall_table);
2092 
2093 	// these are the retail sounds, but in the future we may want to add
2094 	// main hall table configs for other sounds to not truncate
2095 	// (these indexes are the activate/deactivate sounds for the non-door hotspots in the door lists)
2096 	auto retail_indexes = { 32, 33, 34, 35, 43, 44, 56, 57, 54, 55, 51, 52, 25, 61 };
2097 	for (auto idx: retail_indexes) {
2098 		auto iface_entry = gamesnd_get_by_iface_tbl_index(idx);
2099 		if (iface_entry.isValid()) {
2100 			Main_hall_sounds_to_not_truncate.push_back(gamesnd_get_interface_sound(iface_entry));
2101 		}
2102 	}
2103 }
2104 
2105 // read in main hall table
parse_main_hall_table(const char * filename)2106 void parse_main_hall_table(const char* filename)
2107 {
2108 	int hall_idx, res_idx = 0, num_resolutions = 2;
2109 
2110 	try
2111 	{
2112 		read_file_text(filename, CF_TYPE_TABLES);
2113 
2114 		reset_parse();
2115 
2116 		if (optional_string("$Num Resolutions:")) {
2117 			stuff_int(&num_resolutions);
2118 		}
2119 
2120 		if (num_resolutions < 1) {
2121 			Error(LOCATION, "$Num Resolutions in %s is %d. (Must be 1 or greater)", filename, num_resolutions);
2122 		}
2123 
2124 		// find out what hall we read next
2125 		hall_idx = (int)Main_hall_defines.size();
2126 
2127 		while (required_string_either("#End", "$Main Hall"))
2128 			parse_one_main_hall(Parsing_modular_table, num_resolutions, hall_idx, res_idx);
2129 		required_string("#End");
2130 
2131 		// free up memory from parsing the mainhall tbl
2132 		stop_parse();
2133 	}
2134 	catch (const parse::ParseException& e)
2135 	{
2136 		mprintf(("TABLES: Unable to parse '%s'!  Error message = %s.\n", filename, e.what()));
2137 		return;
2138 	}
2139 }
2140 
parse_one_main_hall(bool replace,int num_resolutions,int & hall_idx,int & res_idx)2141 void parse_one_main_hall(bool replace, int num_resolutions, int &hall_idx, int &res_idx)
2142 {
2143 	char temp_string[MAX_FILENAME_LEN];
2144 	SCP_string temp_scp_string;
2145 	bool first_time = false;
2146 	bool create_if_not_found = true;
2147 	main_hall_defines *m;
2148 
2149 	required_string("$Main Hall");
2150 
2151 	// default to the current index
2152 	sprintf(temp_string, "%d", hall_idx);
2153 
2154 	// see if we have a name
2155 	if (optional_string("+Name:"))
2156 	{
2157 		stuff_string(temp_string, F_RAW, MAX_FILENAME_LEN);
2158 
2159 		if (optional_string("+nocreate"))
2160 		{
2161 			if (!replace)
2162 				Warning(LOCATION, "+nocreate flag used for main hall in non-modular table");
2163 			create_if_not_found = false;
2164 		}
2165 
2166 		// we can only create or look up entries if this is the first resolution
2167 		if (res_idx == 0)
2168 		{
2169 			// see if main hall exists already
2170 			int temp_hall_idx = main_hall_get_index(temp_string);
2171 
2172 			// an entry exists
2173 			if (temp_hall_idx >= 0)
2174 			{
2175 				if (!replace)
2176 				{
2177 					error_display(0, "Main hall name '%s' already exists.  All main hall names must be unique; the second entry has been skipped", temp_string);
2178 					if (!skip_to_start_of_string_either("$Main Hall", "#End"))
2179 						error_display(1, "Missing [#End] or [$Main Hall] after duplicate main hall %s", temp_string);
2180 					return;
2181 				}
2182 
2183 				hall_idx = temp_hall_idx;
2184 			}
2185 			// an entry does not exist
2186 			else
2187 			{
2188 				// Don't create main hall if it has +nocreate and is in a modular table.
2189 				if (!create_if_not_found && replace)
2190 				{
2191 					if (!skip_to_start_of_string_either("$Main Hall", "#End"))
2192 						error_display(1, "Missing [#End] or [$Main Hall] after main hall %s", temp_string);
2193 					return;
2194 				}
2195 
2196 				hall_idx = (int)Main_hall_defines.size();
2197 			}
2198 		}
2199 		// if this is not the first resolution, check consistent names
2200 		else
2201 		{
2202 			/**
2203 			 * the reason that this is an error is that even if we were to change the names to match
2204 			 * it is very likely the user would get the wrong mainhall loaded since their campaign files
2205 			 * may still refer to the entry with the incorrect name
2206 			 */
2207 			if (strcmp(temp_string, Main_hall_defines.at(hall_idx).at(0).name.c_str()) != 0)
2208 			{
2209 				error_display(0, "The main hall '%s' has different names for different resolutions. All resolutions must have the same name. Either remove the hi-res entries' names entirely or set them to match the first resolution entry's name.", Main_hall_defines.at(hall_idx).at(0).name.c_str());
2210 				strcpy_s(temp_string, Main_hall_defines.at(hall_idx).at(0).name.c_str());
2211 			}
2212 		}
2213 	}
2214 	// if we don't have a name, but this isn't the first resolution, use the name of the first resolution
2215 	else if (res_idx != 0)
2216 	{
2217 		strcpy_s(temp_string, Main_hall_defines.at(hall_idx).at(0).name.c_str());
2218 	}
2219 
2220 	// now that that's all (optionally) sorted, figure out which entry we're parsing
2221 	if (hall_idx >= (int)Main_hall_defines.size())
2222 	{
2223 		Main_hall_defines.emplace_back();
2224 	}
2225 	if (res_idx >= (int)Main_hall_defines.at(hall_idx).size())
2226 	{
2227 		Main_hall_defines.at(hall_idx).emplace_back();
2228 		first_time = true;
2229 	}
2230 	m = &Main_hall_defines.at(hall_idx).at(res_idx);
2231 	m->name = temp_string;
2232 
2233 	// ---------- now parse the main hall data ----------
2234 
2235 	// add cheats
2236 	while (optional_string("+Cheat String:")) {
2237 		stuff_string(temp_scp_string, F_RAW);
2238 		m->cheat.push_back(temp_scp_string);
2239 
2240 		if (temp_scp_string.size() > MAIN_HALL_MAX_CHEAT_LEN) {
2241 			// Since the value is longer than the cheat buffer it will never match.
2242 			Warning(LOCATION, "The value '%s' for '+Cheat String:' is too long! It can be at most %d characters long.", temp_scp_string.c_str(), MAIN_HALL_MAX_CHEAT_LEN);
2243 		}
2244 
2245 		required_string("+Anim To Change:");
2246 		stuff_string(temp_scp_string, F_NAME);
2247 		m->cheat_anim_from.push_back(temp_scp_string);
2248 
2249 		required_string("+Anim To Change To:");
2250 		stuff_string(temp_scp_string, F_NAME);
2251 		m->cheat_anim_to.push_back(temp_scp_string);
2252 	}
2253 
2254 	// minimum resolution
2255 	if (optional_string("+Min Resolution:")) {
2256 		stuff_int(&m->min_width);
2257 		stuff_int(&m->min_height);
2258 	}
2259 	else if (first_time && res_idx != 0) {
2260 		m->min_width = GR_1024_THRESHOLD_WIDTH;
2261 		m->min_height = GR_1024_THRESHOLD_HEIGHT;
2262 	}
2263 
2264 	// minimum aspect ratio
2265 	if (optional_string("+Min Aspect Ratio:")) {
2266 		stuff_float(&m->min_aspect_ratio);
2267 	}
2268 
2269 	// bitmap and mask
2270 	if (optional_string("+Bitmap:")) {
2271 		stuff_string(temp_string, F_NAME, MAX_FILENAME_LEN);
2272 		m->bitmap = temp_string;
2273 	}
2274 
2275 	if (optional_string("+Mask:")) {
2276 		stuff_string(temp_string, F_NAME, MAX_FILENAME_LEN);
2277 		m->mask = temp_string;
2278 	}
2279 
2280 	if (optional_string("+Music:")) {	// Demo doesn't have these lines.
2281 		stuff_string(temp_string, F_NAME, MAX_FILENAME_LEN);
2282 		m->music_name = temp_string;
2283 	}
2284 
2285 	// Goober5000
2286 	if (optional_string("+Substitute Music:")) {
2287 		stuff_string(temp_string, F_NAME, MAX_FILENAME_LEN);
2288 		m->substitute_music_name = temp_string;
2289 	}
2290 
2291 	if (optional_string("+Help Overlay:")) {
2292 		stuff_string(temp_string, F_NAME, MAX_FILENAME_LEN);
2293 		m->help_overlay_name = temp_string;
2294 	}
2295 
2296 	if (optional_string("+Help Overlay Resolution Index:")) {
2297 		stuff_int(&m->help_overlay_resolution_index);
2298 	}
2299 	else if (first_time) {
2300 		m->help_overlay_resolution_index = res_idx;
2301 	}
2302 
2303 	// zoom area
2304 	if (optional_string("+Zoom To:")) {
2305 		stuff_int(&m->zoom_area_width);
2306 		stuff_int(&m->zoom_area_height);
2307 	}
2308 
2309 	// intercom sounds
2310 	if (optional_string("+Num Intercom Sounds:")) {
2311 		int base_num = m->num_random_intercom_sounds;
2312 		int num;
2313 		stuff_int(&num);
2314 		m->num_random_intercom_sounds += num;
2315 
2316 		// initialise the intercom sounds vectors
2317 		intercom_sounds_init(*m, first_time, base_num);
2318 
2319 		for (int idx = base_num; idx < m->num_random_intercom_sounds; idx++) {
2320 			// intercom delay
2321 			required_string("+Intercom delay:");
2322 			stuff_int(&m->intercom_delay.at(idx).at(0));
2323 			stuff_int(&m->intercom_delay.at(idx).at(1));
2324 		}
2325 
2326 		for (int idx = base_num; idx < m->num_random_intercom_sounds; idx++) {
2327 			// intercom sound id
2328 			parse_iface_sound("+Intercom sound:", &m->intercom_sounds.at(idx));
2329 		}
2330 
2331 		for (int idx = base_num; idx < m->num_random_intercom_sounds; idx++) {
2332 			// intercom pan
2333 			required_string("+Intercom pan:");
2334 			stuff_float(&m->intercom_sound_pan.at(idx));
2335 		}
2336 	}
2337 
2338 	// misc animations
2339 	if (optional_string("+Num Misc Animations:")) {
2340 		int base_num = m->num_misc_animations;
2341 		int num;
2342 		stuff_int(&num);
2343 		m->num_misc_animations += num;
2344 
2345 		// initialise the misc anims vectors
2346 		misc_anim_init(*m, first_time, base_num);
2347 
2348 		for (int idx = base_num; idx < m->num_misc_animations; idx++) {
2349 			// anim names
2350 			required_string("+Misc anim:");
2351 			stuff_string(temp_string, F_NAME, MAX_FILENAME_LEN);
2352 			m->misc_anim_name.at(idx) = temp_string;
2353 		}
2354 
2355 		for (int idx = base_num; idx < m->num_misc_animations; idx++) {
2356 			// anim groups, optionally
2357 			if (optional_string("+Misc anim group:")) {
2358 				stuff_int(&m->misc_anim_group.at(idx));
2359 			}
2360 			else {
2361 				m->misc_anim_group.at(idx) = -1;
2362 			}
2363 		}
2364 
2365 		for (int idx = base_num; idx < m->num_misc_animations; idx++) {
2366 			// anim delay
2367 			required_string("+Misc anim delay:");
2368 			stuff_int(&m->misc_anim_delay.at(idx).at(0));
2369 			stuff_int(&m->misc_anim_delay.at(idx).at(1));
2370 			stuff_int(&m->misc_anim_delay.at(idx).at(2));
2371 		}
2372 
2373 		for (int idx = base_num; idx < m->num_misc_animations; idx++) {
2374 			// anim coords
2375 			required_string("+Misc anim coords:");
2376 			stuff_int(&m->misc_anim_coords.at(idx).at(0));
2377 			stuff_int(&m->misc_anim_coords.at(idx).at(1));
2378 		}
2379 
2380 		for (int idx = base_num; idx < m->num_misc_animations; idx++) {
2381 			// anim mode
2382 			required_string("+Misc anim mode:");
2383 			stuff_int(&m->misc_anim_modes.at(idx));
2384 		}
2385 
2386 		for (int idx = base_num; idx < m->num_misc_animations; idx++) {
2387 			// anim pan
2388 			required_string("+Misc anim pan:");
2389 			stuff_float(&m->misc_anim_sound_pan.at(idx));
2390 		}
2391 
2392 		for (int idx = base_num; idx < m->num_misc_animations; idx++) {
2393 			// anim sound id
2394 			parse_iface_sound_list("+Misc anim sounds:", m->misc_anim_special_sounds.at(idx), "+Misc anim sounds:");
2395 		}
2396 
2397 		for (int idx = base_num; idx < m->num_misc_animations; idx++) {
2398 			// anim sound triggers
2399 			required_string("+Misc anim trigger:");
2400 			int temp_int = 0;
2401 			stuff_int(&temp_int);
2402 			for (int s_idx = 0; s_idx < temp_int; s_idx++) {
2403 				m->misc_anim_special_trigger.at(idx).push_back(0);
2404 				stuff_int(&m->misc_anim_special_trigger.at(idx).at(s_idx));
2405 			}
2406 		}
2407 
2408 		for (int idx = base_num; idx < m->num_misc_animations; idx++) {
2409 			// anim sound handles - deprecated, but deal with it just in case
2410 			if (optional_string("+Misc anim handles:")) {
2411 				advance_to_eoln(nullptr);
2412 			}
2413 		}
2414 
2415 		for (int idx = base_num; idx < m->num_misc_animations; idx++) {
2416 			// anim sound flags - table flag deprecated, so ignore user input
2417 			if (optional_string("+Misc anim flags:")) {
2418 				advance_to_eoln(nullptr);
2419 			}
2420 
2421 			// we need one flag for each sound
2422 			Assert(m->misc_anim_special_sounds.at(idx).size() < INT_MAX);
2423 			for (int s_idx = 0; s_idx < (int)m->misc_anim_special_sounds.at(idx).size(); s_idx++) {
2424 				m->misc_anim_sound_flag.at(idx).push_back(0);
2425 			}
2426 		}
2427 
2428 		for (int idx = base_num; idx < m->num_misc_animations; idx++) {
2429 			// render over doors - default to false
2430 			if (optional_string("+Misc anim over doors:")) {
2431 				bool temp_b;
2432 				stuff_boolean(&temp_b);
2433 				m->misc_anim_over_doors.push_back(temp_b);
2434 			}
2435 			else {
2436 				m->misc_anim_over_doors.push_back(false);
2437 			}
2438 		}
2439 	}
2440 
2441 	if (first_time)
2442 		region_info_init(*m);
2443 
2444 	// door animations
2445 	if (optional_string("+Num Door Animations:")) {
2446 		int base_num = m->num_door_animations;
2447 		int num;
2448 		stuff_int(&num);
2449 		m->num_door_animations += num;
2450 
2451 		// initialise the door anim vectors
2452 		door_anim_init(*m, first_time, base_num);
2453 
2454 		for (int idx = base_num; idx < m->num_door_animations; idx++) {
2455 			// door name
2456 			required_string("+Door anim:");
2457 			stuff_string(temp_string, F_NAME, MAX_FILENAME_LEN);
2458 			m->door_anim_name.at(idx) = temp_string;
2459 		}
2460 
2461 		for (int idx = base_num; idx < m->num_door_animations; idx++) {
2462 			// door coords
2463 			required_string("+Door coords:");
2464 			stuff_int(&m->door_anim_coords.at(idx).at(0));
2465 			stuff_int(&m->door_anim_coords.at(idx).at(1));
2466 			stuff_int(&m->door_anim_coords.at(idx).at(2));
2467 			stuff_int(&m->door_anim_coords.at(idx).at(3));
2468 		}
2469 
2470 		for (int idx = base_num; idx < m->num_door_animations; idx++) {
2471 			// door open and close sounds
2472 			parse_iface_sound_list("+Door sounds:", m->door_sounds.at(idx), "+Door sounds:", true);
2473 		}
2474 
2475 		for (int idx = base_num; idx < m->num_door_animations; idx++) {
2476 			// door pan value
2477 			required_string("+Door pan:");
2478 			stuff_float(&m->door_sound_pan[idx]);
2479 		}
2480 	}
2481 
2482 	int mask;
2483 	for (int idx = 0; optional_string("+Door mask value:"); idx++) {
2484 		// door mask
2485 		stuff_string(temp_string, F_RAW, MAX_FILENAME_LEN);
2486 
2487 		mask = (int)strtol(temp_string, nullptr, 0);
2488 		mask = 255 - mask;
2489 
2490 		if (idx >= (int)m->regions.size()) {
2491 			m->regions.resize(idx + 1);
2492 		}
2493 		m->regions[idx].mask = mask;
2494 	}
2495 
2496 	for (int idx = 0; optional_string("+Door action:"); idx++) {
2497 		// door action
2498 
2499 		if (idx >= (int)m->regions.size()) {
2500 			m->regions.resize(idx + 1);
2501 		}
2502 
2503 		if (optional_string("Script")) {
2504 			m->regions[idx].action = SCRIPT_REGION;
2505 			stuff_string(m->regions[idx].lua_action, F_RAW);
2506 		}
2507 		else {
2508 			stuff_string(temp_scp_string, F_RAW);
2509 
2510 			int action = -1;
2511 			for (int i = 0; Main_hall_region_map[i].name != nullptr; i++) {
2512 				if (temp_scp_string == Main_hall_region_map[i].name) {
2513 					action = Main_hall_region_map[i].mask;
2514 					break;
2515 				}
2516 			}
2517 
2518 			if (action == -1) {
2519 				SCP_string err_msg;
2520 				for (int i = 0; Main_hall_region_map[i].name != nullptr; i++) {
2521 					if (i != 0) {
2522 						err_msg += ", ";
2523 					}
2524 					err_msg += Main_hall_region_map[i].name;
2525 				}
2526 
2527 				Error(LOCATION, "Unkown Door Region '%s'! Expected one of: %s", temp_scp_string.c_str(), err_msg.c_str());
2528 			}
2529 
2530 			m->regions[idx].action = action;
2531 		}
2532 	}
2533 
2534 	for (int idx = 0; optional_string("+Door key:"); idx++) {
2535 		// door key
2536 		stuff_string(temp_string, F_RAW, MAX_FILENAME_LEN);
2537 
2538 		if ((int)m->regions.size() <= idx) {
2539 			m->regions.resize(idx + 1);
2540 		}
2541 		m->regions[idx].key = temp_string[0];
2542 	}
2543 
2544 	for (int idx = 0; optional_string("+Door description:"); idx++) {
2545 		// region description (tooltip)
2546 		stuff_string(temp_scp_string, F_MESSAGE);
2547 
2548 		if (temp_scp_string != "default") {
2549 			if (idx >= (int)m->regions.size()) {
2550 				m->regions.resize(idx + 1);
2551 			}
2552 
2553 			m->regions[idx].description = temp_scp_string;
2554 
2555 			if (idx == 2) {
2556 				m->default_readyroom = false;
2557 			}
2558 		}
2559 	}
2560 
2561 	// font for tooltips and other text
2562 	if (optional_string("+Font:")) {
2563 		m->font = font::parse_font();
2564 	}
2565 
2566 	// tooltip padding
2567 	if (optional_string("+Tooltip Padding:")) {
2568 		stuff_int(&m->tooltip_padding);
2569 	}
2570 
2571 	// tooltip y location
2572 	if (optional_string("+Tooltip Y:")) {
2573 		stuff_int(&m->region_yval);
2574 	}
2575 
2576 	// ---------- done parsing the main hall data ----------
2577 
2578 	// we're done, so move to the next entry
2579 	res_idx++;
2580 	if (res_idx >= num_resolutions)
2581 	{
2582 		res_idx = 0;
2583 		hall_idx = (int)Main_hall_defines.size();	// assume the next main hall is going to be a new one
2584 	}
2585 }
2586 
2587 /**
2588  * Make the vasudan main hall funny
2589  */
main_hall_vasudan_funny()2590 void main_hall_vasudan_funny()
2591 {
2592 	Vasudan_funny = 1;
2593 }
2594 
2595 /**
2596  * Lookup if Vasudan main hall, based upon background graphics
2597  */
main_hall_is_vasudan()2598 bool main_hall_is_vasudan()
2599 {
2600 	return !stricmp(Main_hall->bitmap.c_str(), "vhall") || !stricmp(Main_hall->bitmap.c_str(), "2_vhall");
2601 }
2602 
2603 /**
2604  * Silence sounds on mainhall if we hit a pause mode (ie. lost window focus, minimized, etc)
2605  */
main_hall_pause()2606 void main_hall_pause()
2607 {
2608 	if (Main_hall_paused) {
2609 		return;
2610 	}
2611 
2612 	Main_hall_paused = 1;
2613 
2614 	audiostream_pause(Main_hall_music_handle);
2615 
2616 	main_hall_stop_ambient();
2617 }
2618 
2619 /**
2620  * Recover from a paused state (ie. lost window focus, minimized, etc)
2621  */
main_hall_unpause()2622 void main_hall_unpause()
2623 {
2624 	if (!Main_hall_paused) {
2625 		return;
2626 	}
2627 
2628 	Main_hall_paused = 0;
2629 
2630 	audiostream_unpause(Main_hall_music_handle);
2631 
2632 	main_hall_start_ambient();
2633 }
2634