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