/* INTERFACE.C Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc. and the "Aleph One" developers. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. This license is contained in the file "COPYING", which is included with this source code; it is available online at http://www.gnu.org/licenses/gpl.html Thursday, December 30, 1993 6:56:22 PM Mac specific code..... Friday, July 8, 1994 2:32:44 PM (alain) All old code in here is obsolete. This now has interface for the top-level interface (Begin Game, etcÉ) Saturday, September 10, 1994 12:45:48 AM (alain) the interface gutted again. just the stuff that handles the menu though, the rest stayed the same. Thursday, June 8, 1995 2:56:16 PM (ryan) Pillaged, raped, & burned. (in that order) Jan 30, 2000 (Loren Petrich): Added some typecasts Removed some "static" declarations that conflict with "extern" Surrounded choose_saved_game_to_load with "extern "C"" Feb. 4, 2000 (Loren Petrich): Changed halt() to assert(false) for better debugging Feb. 9, 2000 (Loren Petrich): Changed NUMBER_OF_INTRO_SCREENS to 3 Changed NUMBER_OF_CREDIT_SCREENS to Hamish Sinclair's favorite number Fixed multiple-clicks-necessary problem for too few screens. Was in next_game_state(); set game_state.phase (countdown value) to zero. Feb 19, 2000 (Loren Petrich): Set the single-player color to the player color set in the preferences, for the benefit of chase-cam users. Mar 5, 2000 (Loren Petrich): Added reset_screen() when starting a game, so that extravision will not be persistent. May 13, 2000 (Loren Petrich): Added Rhys Hill's fix for problems with quitting OpenGL Aug 12, 2000 (Loren Petrich): Using object-oriented file handler Aug 24, 2000 (Loren Petrich): Added source selector to calculate_picture_clut(), in order to better deal with object-oriented file handlers Nov 25, 2000 (Loren Petrich): Added support for movies that start at any level, including at the end of a game. Also added end-screen control. Jan 31, 2001 (Loren Petrich): In pause_game(), will stop the liquid faders that are active Jan 25, 2002 (Br'fin (Jeremy Parsons)): Disabled network and network microphone calls under Carbon Feb 27, 2002 (Br'fin (Jeremy Parsons)): Renabled network calls, but not microphone calls under Carbon May 16, 2002 (Woody Zenfell): Enforcing standard player behavior with regard to films and netplay Jun 5, 2002 (Loren Petrich): Added do-nothing "case _revert_game:" in portable_process_screen_click() at the request of Michael Adams. Feb 1, 2003 (Woody Zenfell): Reenabling network microphone support on all platforms, trying to share code and present consistent interfaces to the greatest degree practical. Feb 8-12, 2003 (Woody Zenfell): Introducing support for generalized game startup (will enable resumption of saved-games as netgames, among other things). Feb 13, 2003 (Woody Zenfell): We can now resume games as network games. */ // NEED VISIBLE FEEDBACK WHEN APPLETALK IS NOT AVAILABLE!!! /* ZZZ: more on enforcing standard behavior... + Standard behavior forced when playing a network game. + Standard behavior forced when replaying a film. + Custom behavior allowed when starting or restoring a single-player game. + No film recorded in single-player if custom behavior != standard behavior. Once films and netplay properly record each player's behavior prefs, and the relevant code uses per-player settings, this won't be necessary. Try a mass-search for "player_behavior" to find the areas affected. */ #include "cseries.h" // sorry ryan, nov. 4 #include #include #include #ifdef PERFORMANCE #include extern TP2PerfGlobals perf_globals; #endif #include "map.h" #include "shell.h" #include "interface.h" #include "player.h" #include "network.h" #include "screen_drawing.h" #include "SoundManager.h" #include "fades.h" #include "game_window.h" #include "game_errors.h" #include "Mixer.h" #include "Music.h" #include "images.h" #include "screen.h" #include "network.h" #include "vbl.h" #include "shell.h" #include "preferences.h" #include "FileHandler.h" #include "lua_script.h" // PostIdle #include "interface_menus.h" #include "XML_LevelScript.h" #ifdef HAVE_SMPEG #include #endif #include "sdl_dialogs.h" #include "sdl_widgets.h" #include "network_dialog_widgets_sdl.h" /* Change this when marathon changes & replays are no longer valid */ enum recording_version { aleph_recording_version= 0, }; const short default_recording_version= aleph_recording_version; const short max_handled_recording= aleph_recording_version; #include "screen_definitions.h" #include "interface_menus.h" // LP addition: getting OpenGL rendering stuff #include "render.h" #include "OGL_Render.h" #include "alephversion.h" // To tell it to stop playing, // and also to run the end-game script #include "XML_LevelScript.h" // Network microphone/speaker #include "network_sound.h" #include "network_distribution_types.h" // ZZZ: should the function that uses these (join_networked_resume_game()) go elsewhere? #include "wad.h" #include "game_wad.h" #include "motion_sensor.h" // for reset_motion_sensor() #ifdef env68k #pragma segment shell #endif /* ------------- enums */ /* ------------- constants */ #define DISPLAY_PICT_RESOURCE_TYPE 'PICT' #define CLOSE_WITHOUT_WARNING_DELAY (5*TICKS_PER_SECOND) #define NUMBER_OF_INTRO_SCREENS (3) #define INTRO_SCREEN_DURATION (215 * MACHINE_TICKS_PER_SECOND / TICKS_PER_SECOND) // fudge to align with sound #ifdef DEMO #define INTRO_SCREEN_TO_START_SONG_ON (1) #else #define INTRO_SCREEN_TO_START_SONG_ON (0) #endif #define INTRO_SCREEN_BETWEEN_DEMO_BASE (INTRO_SCREEN_BASE+1) /* +1 to get past the powercomputing */ #define NUMBER_OF_INTRO_SCREENS_BETWEEN_DEMOS (1) #define DEMO_INTRO_SCREEN_DURATION (10 * MACHINE_TICKS_PER_SECOND) #define TICKS_UNTIL_DEMO_STARTS (30 * MACHINE_TICKS_PER_SECOND) #define NUMBER_OF_PROLOGUE_SCREENS 0 #define PROLOGUE_DURATION (10 * MACHINE_TICKS_PER_SECOND) #define NUMBER_OF_EPILOGUE_SCREENS 1 #define EPILOGUE_DURATION (INDEFINATE_TIME_DELAY) #define NUMBER_OF_CREDIT_SCREENS 7 #define CREDIT_SCREEN_DURATION (15 * 60 * MACHINE_TICKS_PER_SECOND) #define NUMBER_OF_CHAPTER_HEADINGS 0 #define CHAPTER_HEADING_DURATION (7*MACHINE_TICKS_PER_SECOND) // For exiting the Marathon app // #if defined(DEBUG) || !defined(DEMO) #define NUMBER_OF_FINAL_SCREENS 0 // #else // #define NUMBER_OF_FINAL_SCREENS 1 // #endif #define FINAL_SCREEN_DURATION (INDEFINATE_TIME_DELAY) /* For teleportation, end movie, etc. */ #define EPILOGUE_LEVEL_NUMBER 256 /* ------------- structures */ struct game_state { short state; short flags; short user; long phase; long last_ticks_on_idle; short current_screen; bool suppress_background_tasks; bool current_netgame_allows_microphone; short main_menu_display_count; // how many times have we shown the main menu? }; struct screen_data { short screen_base; short screen_count; long duration; }; /* -------------- constants */ struct screen_data display_screens[]= { { INTRO_SCREEN_BASE, NUMBER_OF_INTRO_SCREENS, INTRO_SCREEN_DURATION }, { MAIN_MENU_BASE, 1, 0 }, { CHAPTER_SCREEN_BASE, NUMBER_OF_CHAPTER_HEADINGS, CHAPTER_HEADING_DURATION }, { PROLOGUE_SCREEN_BASE, NUMBER_OF_PROLOGUE_SCREENS, PROLOGUE_DURATION }, { EPILOGUE_SCREEN_BASE, NUMBER_OF_EPILOGUE_SCREENS, EPILOGUE_DURATION }, { CREDIT_SCREEN_BASE, NUMBER_OF_CREDIT_SCREENS, CREDIT_SCREEN_DURATION}, { INTRO_SCREEN_BETWEEN_DEMO_BASE, NUMBER_OF_INTRO_SCREENS_BETWEEN_DEMOS, DEMO_INTRO_SCREEN_DURATION }, { FINAL_SCREEN_BASE, NUMBER_OF_FINAL_SCREENS, FINAL_SCREEN_DURATION } }; #if 0 struct chapter_screen_sound_data { short level; short sound_code; }; struct chapter_screen_sound_data chapter_screen_sounds[]= { {0, _snd_chapter1}, {1, _snd_chapter2}, {2, _snd_chapter3} }; #define NUMBER_OF_CHAPTER_SCREEN_SOUNDS (sizeof(chapter_screen_sounds)/sizeof(chapter_screen_sounds[1])) #endif /* -------------- local globals */ static struct game_state game_state; static FileSpecifier DraggedReplayFile; static bool interface_fade_in_progress= false; static short interface_fade_type; static short current_picture_clut_depth; static struct color_table *animated_color_table= NULL; static struct color_table *current_picture_clut= NULL; /* -------------- externs */ extern short interface_bit_depth; extern short bit_depth; /* ----------- prototypes/PREPROCESS_MAP_MAC.C */ extern bool load_game_from_file(FileSpecifier& File); extern bool choose_saved_game_to_load(FileSpecifier& File); /* ---------------------- prototypes */ static void display_credits(void); static void draw_button(short index, bool pressed); static void handle_replay(bool last_replay); static bool begin_game(short user, bool cheat); static void start_game(short user, bool changing_level); // LP: "static" removed void handle_load_game(void); static void handle_save_film(void); static void finish_game(bool return_to_main_menu); static void clean_up_after_failed_game(bool inNetgame, bool inRecording, bool inFullCleanup); static void handle_network_game(bool gatherer); static void next_game_screen(void); static void handle_interface_menu_screen_click(short x, short y, bool cheatkeys_down); static void display_introduction(void); static void display_loading_map_error(void); static void display_quit_screens(void); static void display_screen(short base_pict_id); static void display_introduction_screen_for_demo(void); static void display_epilogue(void); static void force_system_colors(void); static bool point_in_rectangle(short x, short y, screen_rectangle *rect); static void start_interface_fade(short type, struct color_table *original_color_table); static void update_interface_fades(void); static void interface_fade_out(short pict_resource_number, bool fade_music); static bool can_interface_fade_out(void); static void transfer_to_new_level(short level_number); static void try_and_display_chapter_screen(short level, bool interface_table_is_valid, bool text_block); static screen_data *get_screen_data( short index); /* ---------------------- code begins */ screen_data *get_screen_data( short index) { assert(index>=0 && indexdifficulty_level; } // ----- ZZZ start support for generalized game startup ----- // (should this be split out (with some other game startup code) to a new file?) // In this scheme, a "start" corresponds to a player available at the moment, who will // participate in the game we're starting up. In general when this code talks about a // 'player', it is referring to an already-existing player in the game world (which should // already be restored or initialized fairly well before these routines are used). // Generalized game startup will match starts to existing players, set leftover players // as "zombies" so they exist but just stand there, and create new players to correspond // with any remaining starts. As such it can handle both resume-game (some players already // exist) and new-game (dynamic_world->player_count == 0) operations. static void construct_single_player_start(player_start_data* outStartArray, short* outStartCount) { if(outStartCount != NULL) { *outStartCount = 1; } outStartArray[0].team = player_preferences->color; outStartArray[0].color = player_preferences->color; outStartArray[0].identifier = 0; memcpy(outStartArray[0].name, &(player_preferences->name[1]), player_preferences->name[0]); outStartArray[0].name[player_preferences->name[0]] = '\0'; set_player_start_doesnt_auto_recenter_status(&outStartArray[0], dont_auto_recenter()); set_player_start_doesnt_auto_switch_weapons_status(&outStartArray[0], dont_switch_to_new_weapon()); } #if !defined(DISABLE_NETWORKING) static void construct_multiplayer_starts(player_start_data* outStartArray, short* outStartCount) { int number_of_players = NetGetNumberOfPlayers(); if(outStartCount != NULL) { *outStartCount = number_of_players; } for(int player_index= 0; player_indexteam; outStartArray[player_index].color= player_information->color; outStartArray[player_index].identifier = NetGetPlayerIdentifier(player_index); /* Copy and translate from pascal string to cstring */ memcpy(outStartArray[player_index].name, &player_information->name[1], player_information->name[0]); outStartArray[player_index].name[player_information->name[0]]= 0; } } #endif // !defined(DISABLE_NETWORKING) // This should be safe to use whether starting or resuming and whether single-player or multiplayer. void match_starts_with_existing_players(player_start_data* ioStartArray, short* ioStartCount) { // This code could be smarter, but it doesn't run very often, doesn't get big data sets, etc. // so I'm not going to worry about it. bool startAssigned[MAXIMUM_NUMBER_OF_PLAYERS]; int8 startAssignedToPlayer[MAXIMUM_NUMBER_OF_PLAYERS]; for(int i = 0; i < MAXIMUM_NUMBER_OF_PLAYERS; i++) { startAssigned[i] = false; startAssignedToPlayer[i] = NONE; } // First, match starts to players by name. for(int s = 0; s < *ioStartCount; s++) { for(int p = 0; p < dynamic_world->player_count; p++) { if(startAssignedToPlayer[p] == NONE) { if(strcmp(ioStartArray[s].name, get_player_data(p)->name) == 0) { startAssignedToPlayer[p] = s; startAssigned[s] = true; break; } } } } // Match remaining starts to remaining players arbitrarily. for(int s = 0; s < *ioStartCount; s++) { if(!startAssigned[s]) { for(int p = 0; p < dynamic_world->player_count; p++) { if(startAssignedToPlayer[p] == NONE) { startAssignedToPlayer[p] = s; startAssigned[s] = true; break; } } } } // Create new starts for any players not covered. int p = 0; while(*ioStartCount < dynamic_world->player_count) { if(startAssignedToPlayer[p] == NONE) { player_data* thePlayer = get_player_data(p); ioStartArray[*ioStartCount].team = thePlayer->team; ioStartArray[*ioStartCount].color = thePlayer->color; ioStartArray[*ioStartCount].identifier = NONE; strcpy(ioStartArray[*ioStartCount].name, thePlayer->name); startAssignedToPlayer[p] = *ioStartCount; startAssigned[*ioStartCount] = true; (*ioStartCount)++; } p++; } // Assign remaining starts to players that don't exist yet p = dynamic_world->player_count; for(int s = 0; s < *ioStartCount; s++) { if(!startAssigned[s]) { startAssignedToPlayer[p] = s; startAssigned[s] = true; p++; } } // Reorder starts to match players - this is particularly unclever player_start_data theOriginalStarts[MAXIMUM_NUMBER_OF_PLAYERS]; memcpy(theOriginalStarts, ioStartArray, sizeof(theOriginalStarts)); for(p = 0; p < *ioStartCount; p++) { ioStartArray[p] = theOriginalStarts[startAssignedToPlayer[p]]; } } // This should be safe to use whether starting or resuming, and whether single- or multiplayer. static void synchronize_players_with_starts(const player_start_data* inStartArray, short inStartCount) { // s will walk through all the starts int s = 0; // First we process existing players for( ; s < dynamic_world->player_count; s++) { player_data* thePlayer = get_player_data(s); if(inStartArray[s].identifier == NONE) { // No start (live player) to go with this player (stored player) SET_PLAYER_ZOMBIE_STATUS(thePlayer, true); } else { // Update player's appearance to match the start thePlayer->team = inStartArray[s].team; thePlayer->color = inStartArray[s].color; thePlayer->identifier = player_identifier_value(inStartArray[s].identifier); strcpy(thePlayer->name, inStartArray[s].name); SET_PLAYER_DOESNT_AUTO_RECENTER_STATUS(thePlayer, player_identifier_doesnt_auto_recenter(inStartArray[s].identifier)); SET_PLAYER_DOESNT_AUTO_SWITCH_WEAPONS_STATUS(thePlayer, player_identifier_doesnt_auto_switch_weapons(inStartArray[s].identifier)); // Make sure if player was saved as zombie, they're not now. SET_PLAYER_ZOMBIE_STATUS(thePlayer, false); } } // If there are any starts left, we need new players for them for( ; s < inStartCount; s++) { int theIndex = new_player(inStartArray[s].team, inStartArray[s].color, inStartArray[s].identifier); assert(theIndex == s); player_data* thePlayer = get_player_data(theIndex); strcpy(thePlayer->name, inStartArray[s].name); } } static short find_start_for_identifier(const player_start_data* inStartArray, short inStartCount, short _inIdentifier) { short inIdentifier= player_identifier_value(_inIdentifier); short s; for(s = 0; s < inStartCount; s++) { if(player_identifier_value(inStartArray[s].identifier) == inIdentifier) { break; } } return (s == inStartCount) ? NONE : s; } // The single-player machine, gatherer, and joiners all will use this routine. It should take most of its // cues from the "extras" that load_game_from_file() does. static bool make_restored_game_relevant(bool inNetgame, const player_start_data* inStartArray, short inStartCount) { game_is_networked = inNetgame; // set_random_seed() needs to happen before synchronize_players_with_starts() // since the latter calls new_player() which almost certainly uses global_random(). // Note we always take the random seed directly from the dynamic_world, no need to screw around // with copying it from game_information or the like. set_random_seed(dynamic_world->random_seed); synchronize_players_with_starts(inStartArray, inStartCount); short theLocalPlayerIndex; #if !defined(DISABLE_NETWORKING) // Much of the code in this if()...else is very similar to code in begin_game(), should probably try to share. if(inNetgame) { game_info *network_game_info= (game_info *)NetGetGameData(); dynamic_world->game_information.game_time_remaining= network_game_info->time_limit; dynamic_world->game_information.kill_limit= network_game_info->kill_limit; dynamic_world->game_information.game_type= network_game_info->net_game_type; dynamic_world->game_information.game_options= network_game_info->game_options; dynamic_world->game_information.initial_random_seed= network_game_info->initial_random_seed; dynamic_world->game_information.difficulty_level= network_game_info->difficulty_level; if (network_game_info->allow_mic) { install_network_microphone(); game_state.current_netgame_allows_microphone= true; } else { game_state.current_netgame_allows_microphone= false; } // ZZZ: until players specify their behavior modifiers over the network, // to avoid out-of-sync we must force them all the same. standardize_player_behavior_modifiers(); theLocalPlayerIndex = NetGetLocalPlayerIndex(); } else #endif // !defined(DISABLE_NETWORKING) { dynamic_world->game_information.difficulty_level= get_difficulty_level(); restore_custom_player_behavior_modifiers(); theLocalPlayerIndex = find_start_for_identifier(inStartArray, inStartCount, 0); } assert(theLocalPlayerIndex != NONE); set_local_player_index(theLocalPlayerIndex); set_current_player_index(theLocalPlayerIndex); bool success = entering_map(true /*restoring game*/); reset_motion_sensor(theLocalPlayerIndex); if(!success) clean_up_after_failed_game(inNetgame, false /*recording*/, true /*full cleanup*/); return success; } // ZZZ end generalized game startup support ----- #if !defined(DISABLE_NETWORKING) // ZZZ: this will get called (eventually) shortly after NetUpdateJoinState() returns netStartingResumeGame bool join_networked_resume_game() { bool success = true; // Get the saved-game data byte* theSavedGameFlatData = NetReceiveGameData(false /* do_physics */); if(theSavedGameFlatData == NULL) { success = false; } if(success) { // Use the saved-game data wad_header theWadHeader; wad_data* theWad; theWad = inflate_flat_data(theSavedGameFlatData, &theWadHeader); if(theWad == NULL) { success = false; free(theSavedGameFlatData); } if(success) { success = process_map_wad(theWad, true /* resuming */, theWadHeader.data_version); free_wad(theWad); /* Note that the flat data points into the wad. */ // ZZZ: maybe this is what the Bungie comment meant, but apparently // free_wad() somehow (voodoo) frees theSavedGameFlatData as well. } if(success) { // try to locate the Map file for the saved-game, so that (1) we have a crack // at continuing the game if the original gatherer disappears, and (2) we can // save the game on our own machine and continue it properly (as part of a bigger scenario) later. uint32 theParentChecksum = theWadHeader.parent_checksum; if(use_map_file(theParentChecksum)) { // LP: getting the level scripting off of the map file // Being careful to carry over errors so that Pfhortran errors can be ignored short SavedType, SavedError = get_game_error(&SavedType); RunLevelScript(dynamic_world->current_level_number); set_game_error(SavedType,SavedError); } else { /* Tell the user theyÕre screwed when they try to leave this level. */ // ZZZ: should really issue a different warning since the ramifications are different alert_user(infoError, strERRORS, cantFindMap, 0); // LP addition: makes the game look normal hide_cursor(); /* Set to the default map. */ set_to_default_map(); } // set the revert-game info to defaults (for full-auto saving on the local machine) set_saved_game_name_to_default(); player_start_data theStarts[MAXIMUM_NUMBER_OF_PLAYERS]; short theStartCount; construct_multiplayer_starts(theStarts, &theStartCount); success = make_restored_game_relevant(true /* multiplayer */, theStarts, theStartCount); } } if(success) start_game(_network_player, false /*changing level?*/); return success; } #endif // !defined(DISABLE_NETWORKING) extern bool load_and_start_game(FileSpecifier& File); // ZZZ: changes to use generalized game startup support // This will be used only on the machine that picked "Continue Saved Game". bool load_and_start_game(FileSpecifier& File) { bool success; hide_cursor(); if (can_interface_fade_out()) { interface_fade_out(MAIN_MENU_BASE, true); } success= load_game_from_file(File); if (!success) { /* Reset the system colors, since the screen clut is all black.. */ force_system_colors(); show_cursor(); // JTP: Was hidden by force system colors display_loading_map_error(); } bool userWantsMultiplayer; size_t theResult = UNONE; if (success) { #ifdef __MACOS__ theResult = 0; #else theResult = should_restore_game_networked(); #endif } if (theResult == UNONE) { // cancelled success = false; } else { userWantsMultiplayer = (theResult != 0); } if (success) { #if !defined(DISABLE_NETWORKING) if (userWantsMultiplayer) { set_game_state(_displaying_network_game_dialogs); success = network_gather(true /*resuming*/); } #endif // !defined(DISABLE_NETWORKING) if (success) { player_start_data theStarts[MAXIMUM_NUMBER_OF_PLAYERS]; short theNumberOfStarts; #if !defined(DISABLE_NETWORKING) if (userWantsMultiplayer) { construct_multiplayer_starts(theStarts, &theNumberOfStarts); } else #endif // !defined(DISABLE_NETWORKING) { construct_single_player_start(theStarts, &theNumberOfStarts); } match_starts_with_existing_players(theStarts, &theNumberOfStarts); #if !defined(DISABLE_NETWORKING) if (userWantsMultiplayer) { NetSetupTopologyFromStarts(theStarts, theNumberOfStarts); success = NetStart(); if (success) { byte* theSavedGameFlatData = (byte*)get_flat_data(File, false /* union wad? */, 0 /* level # */); if (theSavedGameFlatData == NULL) { success = false; } if (success) { long theSavedGameFlatDataLength = get_flat_data_length(theSavedGameFlatData); OSErr theError = NetDistributeGameDataToAllPlayers(theSavedGameFlatData, theSavedGameFlatDataLength, false /* do_physics? */); if (theError != noErr) { success = false; } free(theSavedGameFlatData); } } } #endif // !defined(DISABLE_NETWORKING) if (success) { success = make_restored_game_relevant(userWantsMultiplayer, theStarts, theNumberOfStarts); if (success) { start_game(userWantsMultiplayer ? _network_player : _single_player, false); } } } } if (!success) { /* We failed. Balance the cursor */ /* Should this also force the system colors or something? */ show_cursor(); } return success; } extern bool handle_open_replay(FileSpecifier& File); bool handle_open_replay(FileSpecifier& File) { DraggedReplayFile = File; bool success; force_system_colors(); success= begin_game(_replay_from_file, false); if(!success) display_main_menu(); return success; } // Called from within update_world.. bool check_level_change( void) { bool level_changed= false; if(game_state.state==_change_level) { transfer_to_new_level(game_state.current_screen); level_changed= true; } return level_changed; } void pause_game( void) { stop_fade(); if (!OGL_IsActive() || !(TEST_FLAG(Get_OGL_ConfigureData().Flags,OGL_Flag_Fader))) set_fade_effect(NONE); darken_world_window(); set_keyboard_controller_status(false); #ifdef SDL show_cursor(); #endif } void resume_game( void) { #ifdef SDL hide_cursor(); #endif if (!OGL_IsActive() || !(TEST_FLAG(Get_OGL_ConfigureData().Flags,OGL_Flag_Fader))) SetFadeEffectDelay(TICKS_PER_SECOND/2); validate_world_window(); set_keyboard_controller_status(true); } void draw_menu_button_for_command( short index) { short rectangle_index= index-1+_new_game_button_rect; assert(get_game_state()==_display_main_menu); /* Draw it initially depressed.. */ draw_button(rectangle_index, true); #ifdef SDL SDL_Delay(1000 / 12); #else uint32 initial_tick= machine_tick_count(); while(machine_tick_count()-initial_tick<5) /* 1/12 second */ ; #endif draw_button(rectangle_index, false); } void update_interface_display( void) { struct screen_data *data; data= get_screen_data(game_state.state); /* Use this to avoid the fade.. */ draw_full_screen_pict_resource_from_images(data->screen_base+game_state.current_screen); #if defined(MAC_SDL_KLUDGE) draw_full_screen_pict_resource_from_images(data->screen_base+game_state.current_screen); #endif } void idle_game_state( void) { short machine_ticks_elapsed; machine_ticks_elapsed = (machine_tick_count() - game_state.last_ticks_on_idle); if(machine_ticks_elapsed || game_state.phase==0) { if(game_state.phase != INDEFINATE_TIME_DELAY) { game_state.phase-= machine_ticks_elapsed; } /* Note that we still go through this if we have an indefinate phase.. */ if(game_state.phase<=0) { switch(get_game_state()) { case _display_quit_screens: case _display_intro_screens: case _display_prologue: case _display_epilogue: case _display_credits: next_game_screen(); break; case _display_intro_screens_for_demo: case _display_main_menu: /* Start the demo.. */ if(!begin_game(_demo, false)) { /* This means that there was not a valid demo to play */ game_state.phase= TICKS_UNTIL_DEMO_STARTS; } break; case _close_game: display_main_menu(); break; case _switch_demo: /* This is deferred to the idle task because it */ /* occurs at interrupt time.. */ switch(game_state.user) { case _replay: finish_game(true); break; case _demo: finish_game(false); display_introduction_screen_for_demo(); break; default: assert(false); break; } break; case _display_chapter_heading: dprintf("Chapter heading..."); break; case _quit_game: /* About to quit, but can still hit this through order of ops.. */ break; case _revert_game: /* Reverting while in the update loop sounds sketchy.. */ if(revert_game()) { game_state.state= _game_in_progress; game_state.phase = 15 * MACHINE_TICKS_PER_SECOND; game_state.last_ticks_on_idle= machine_tick_count(); update_interface(NONE); } else { /* Give them the error... */ display_loading_map_error(); /* And finish their current game.. */ finish_game(true); } break; case _begin_display_of_epilogue: finish_game(false); display_epilogue(); break; case _game_in_progress: game_state.phase = 15 * MACHINE_TICKS_PER_SECOND; game_state.last_ticks_on_idle= machine_tick_count(); break; case _change_level: case _displaying_network_game_dialogs: break; default: assert(false); break; } } game_state.last_ticks_on_idle= machine_tick_count(); } /* if weÕre not paused and thereÕs something to draw (i.e., anything different from last time), render a frame */ if(game_state.state==_game_in_progress) { // ZZZ change: update_world() whether or not get_keyboard_controller_status() is true // This way we won't fill up queues and stall netgames if one player switches out for a bit. std::pair theUpdateResult= update_world(); short ticks_elapsed= theUpdateResult.second; if (get_keyboard_controller_status()) { // ZZZ: I don't know for sure that render_screen works best with the number of _real_ // ticks elapsed rather than the number of (potentially predictive) ticks elapsed. // This is a guess. if (theUpdateResult.first) render_screen(ticks_elapsed); } } else { /* Update the fade ins, etc.. */ update_interface_fades(); } } void display_main_menu( void) { game_state.state= _display_main_menu; game_state.current_screen= 0; game_state.phase= TICKS_UNTIL_DEMO_STARTS; game_state.last_ticks_on_idle= machine_tick_count(); game_state.user= _single_player; game_state.flags= 0; display_screen(MAIN_MENU_BASE); /* Start up the song! */ if(!Music::instance()->Playing() && game_state.main_menu_display_count==0) { Music::instance()->RestartIntroMusic(); } // Draw AlephOne Version to screen FontSpecifier& Font = GetOnScreenFont(); #if defined(mac) extern WindowPtr screen_window; WindowPtr window= screen_window; GrafPtr old_port, port; port = GetWindowPort(window); GetPort(&old_port); SetPort(port); Font.Use(); Rect portRect; GetPortBounds(port, &portRect); short X0 = portRect.right; short Y0 = portRect.bottom; #elif defined(SDL) // JTP: This works, but I don't know correctness SDL_Surface *world_pixels = SDL_GetVideoSurface(); short X0 = world_pixels->w; short Y0 = world_pixels->h; #endif // The line spacing is a generalization of "5" for larger fonts short Offset = Font.LineSpacing / 3; short RightJustOffset = Font.TextWidth(A1_VERSION_STRING); short X = X0 - RightJustOffset; short Y = Y0 - Offset; #if defined(mac) MoveTo(X, Y); RGBForeColor(&(RGBColor){0x4000, 0x4000, 0x4000}); DrawString("\p"A1_VERSION_STRING); RGBForeColor(&rgb_black); SetPort(old_port); #elif defined(SDL) // ZZZ: this still won't work for fullscreen (drawing to wrong surface as usual), but at // least in windowed mode it will draw. _set_port_to_screen_window(); draw_text(world_pixels, A1_VERSION_STRING, X, Y, SDL_MapRGB(world_pixels->format, 0x40, 0x40, 0x40), Font.Info, Font.Style); _restore_port(); #endif game_state.main_menu_display_count++; } // Kludge for Carbon/Classic -- when exiting a main-menu dialog box, redisplay static void ForceRepaintMenuDisplay() { #ifdef mac if (!system_information->has_ten || system_information->machine_is_bluebox) { if (get_game_state() == _display_main_menu) display_screen(MAIN_MENU_BASE); } #endif } void do_menu_item_command( short menu_id, short menu_item, bool cheat) { #ifdef SDL static bool pre_pause_fullscreen = false; #endif switch(menu_id) { case mGame: switch(menu_item) { case iPause: switch(game_state.user) { case _single_player: case _replay: if (get_keyboard_controller_status()) { #ifdef SDL // Switch out of fullscreen mode (this is not done in pause_game() // because pause_game() is also called when saving/quitting where we // don't want a mode switch if (get_screen_mode()->fullscreen) { pre_pause_fullscreen = true; toggle_fullscreen(false); render_screen(0); } else { pre_pause_fullscreen = false; } #endif pause_game(); } else { #ifdef SDL // Restore fullscreen mode if (pre_pause_fullscreen) { toggle_fullscreen(true); render_screen(0); } #endif resume_game(); } break; case _demo: finish_game(true); break; case _network_player: break; default: assert(false); break; } break; case iSave: switch(game_state.user) { case _single_player: #if 0 save_game(); validate_world_window(); #endif break; case _demo: case _replay: finish_game(true); break; case _network_player: break; default: assert(false); break; } break; case iRevert: /* Not implemented.. */ break; case iCloseGame: case iQuitGame: { bool really_wants_to_quit= false; switch(game_state.user) { case _single_player: if(PLAYER_IS_DEAD(local_player) || dynamic_world->tick_count-local_player->ticks_at_last_successful_savemachine_has_network_memory) { handle_network_game(false); } else { alert_user(infoError, strERRORS, notEnoughNetworkMemory, 0); } #elif defined(__MACOS__) break; #else handle_network_game(false); #endif break; case iGatherGame: #ifdef mac if (system_information->machine_has_network_memory) { handle_network_game(true); } else { alert_user(infoError, strERRORS, notEnoughNetworkMemory, 0); } #elif defined(__MACOS__) break; #else handle_network_game(true); #endif break; case iLoadGame: handle_load_game(); break; case iReplayLastFilm: case iReplaySavedFilm: handle_replay(menu_item==iReplayLastFilm); break; case iCredits: display_credits(); break; case iPreferences: do_preferences(); game_state.phase= TICKS_UNTIL_DEMO_STARTS; game_state.last_ticks_on_idle= machine_tick_count(); ForceRepaintMenuDisplay(); break; case iCenterButton: break; case iSaveLastFilm: handle_save_film(); break; case iQuit: display_quit_screens(); break; default: assert(false); break; } break; default: assert(false); break; } } void portable_process_screen_click( short x, short y, bool cheatkeys_down) { switch(get_game_state()) { case _game_in_progress: case _begin_display_of_epilogue: case _change_level: case _displaying_network_game_dialogs: case _quit_game: case _close_game: case _switch_demo: case _revert_game: break; case _display_intro_screens_for_demo: /* Get out of user mode. */ display_main_menu(); break; case _display_quit_screens: case _display_intro_screens: case _display_chapter_heading: case _display_prologue: case _display_epilogue: case _display_credits: /* Force the state change next time through.. */ force_game_state_change(); break; case _display_main_menu: handle_interface_menu_screen_click(x, y, cheatkeys_down); break; default: assert(false); break; } } bool enabled_item( short item) { bool enabled= true; switch(item) { case iNewGame: case iLoadGame: case iPlaySingletonLevel: case iPreferences: case iReplaySavedFilm: case iCredits: case iQuit: break; case iCenterButton: enabled= false; break; case iReplayLastFilm: case iSaveLastFilm: enabled= has_recording_file(); break; case iGatherGame: case iJoinGame: enabled= networking_available(); break; default: assert(false); break; } return enabled; } void paint_window_black( void) { _set_port_to_screen_window(); _erase_screen(_black_color); _restore_port(); } /* --------------------- static code */ static void display_introduction( void) { struct screen_data *screen_data= get_screen_data(_display_intro_screens); paint_window_black(); game_state.state= _display_intro_screens; game_state.current_screen= 0; if (screen_data->screen_count) { if (game_state.state==_display_intro_screens && game_state.current_screen==INTRO_SCREEN_TO_START_SONG_ON) { Music::instance()->RestartIntroMusic(); } game_state.phase= screen_data->duration; game_state.last_ticks_on_idle= machine_tick_count(); display_screen(screen_data->screen_base); } else { display_main_menu(); } } static void display_introduction_screen_for_demo( void) { struct screen_data *screen_data= get_screen_data(_display_intro_screens_for_demo); game_state.state= _display_intro_screens_for_demo; game_state.current_screen= 0; if(screen_data->screen_count) { game_state.phase= screen_data->duration; game_state.last_ticks_on_idle= machine_tick_count(); display_screen(screen_data->screen_base); } else { display_main_menu(); } } static void display_epilogue( void) { Music::instance()->RestartIntroMusic(); { long ticks= machine_tick_count(); do { Music::instance()->Idle(); } while (machine_tick_count()-ticks<10); } game_state.state= _display_epilogue; game_state.phase= 0; game_state.current_screen= 0; game_state.last_ticks_on_idle= machine_tick_count(); hide_cursor(); // Setting of the end-screen parameters has been moved to XML_LevelScript.cpp for (int i=0; iduration; game_state.last_ticks_on_idle= machine_tick_count(); display_screen(screen_data->screen_base); } } static void display_quit_screens( void) { struct screen_data *screen_data= get_screen_data(_display_quit_screens); if(screen_data->screen_count) { game_state.state= _display_quit_screens; game_state.current_screen= 0; game_state.user= _single_player; game_state.flags= 0; game_state.phase= screen_data->duration; game_state.last_ticks_on_idle= machine_tick_count(); display_screen(screen_data->screen_base); } else { /* No screens. */ game_state.state= _quit_game; game_state.phase= 0; } } static void transfer_to_new_level( short level_number) { struct entry_point entry; bool success= true; entry.level_number= level_number; #if !defined(DISABLE_NETWORKING) /* Only can transfer if NetUnSync returns true */ if(game_is_networked) { if(NetUnSync()) { success= true; } else { set_game_error(gameError, errUnsyncOnLevelChange); success= false; } } #endif // !defined(DISABLE_NETWORKING) if(success) { stop_fade(); set_fade_effect(NONE); Music::instance()->StopLevelMusic(); //#ifdef mac // if(OGL_IsActive()) { exit_screen(); // Enter_screen will be called again in start_game } //#endif set_keyboard_controller_status(false); FindLevelMovie(entry.level_number); show_movie(entry.level_number); // if this is the EPILOGUE_LEVEL_NUMBER, then it is time to get // out of here already (as we've just played the epilogue movie, // we can move on to the _display_epilogue game state) if (level_number == EPILOGUE_LEVEL_NUMBER) { finish_game(false); show_cursor(); // for some reason, cursor stays hidden otherwise set_game_state(_begin_display_of_epilogue); force_game_state_change(); return; } if (!game_is_networked) try_and_display_chapter_screen(CHAPTER_SCREEN_BASE + level_number, true, false); success= goto_level(&entry, false); set_keyboard_controller_status(true); } if(success) { start_game(game_state.user, true); } else { display_loading_map_error(); finish_game(true); } } /* The port is set.. */ static void draw_button( short index, bool pressed) { screen_rectangle *screen_rect= get_interface_rectangle(index); short pict_resource_number= MAIN_MENU_BASE + pressed; set_drawing_clip_rectangle(screen_rect->top, screen_rect->left, screen_rect->bottom, screen_rect->right); /* Use this to avoid the fade.. */ draw_full_screen_pict_resource_from_images(pict_resource_number); set_drawing_clip_rectangle(SHRT_MIN, SHRT_MIN, SHRT_MAX, SHRT_MAX); } static void handle_replay( /* This is gross. */ bool last_replay) { bool success; if(!last_replay) force_system_colors(); success= begin_game(_replay, !last_replay); if(!success) display_main_menu(); } // ZZZ: some modifications to use generalized game-startup static bool begin_game( short user, bool cheat) { struct entry_point entry; struct player_start_data starts[MAXIMUM_NUMBER_OF_PLAYERS]; struct game_data game_information; short number_of_players; bool success= true; bool is_networked= false; bool clean_up_on_failure= true; bool record_game= false; objlist_clear(starts, MAXIMUM_NUMBER_OF_PLAYERS); switch(user) { case _network_player: #if !defined(DISABLE_NETWORKING) { game_info *network_game_info= (game_info *)NetGetGameData(); construct_multiplayer_starts(starts, &number_of_players); game_information.game_time_remaining= network_game_info->time_limit; game_information.kill_limit= network_game_info->kill_limit; game_information.game_type= network_game_info->net_game_type; game_information.game_options= network_game_info->game_options; game_information.initial_random_seed= network_game_info->initial_random_seed; game_information.difficulty_level= network_game_info->difficulty_level; entry.level_number = network_game_info->level_number; entry.level_name[0] = 0; if (network_game_info->allow_mic) { install_network_microphone(); game_state.current_netgame_allows_microphone= true; } else { game_state.current_netgame_allows_microphone= false; } game_information.cheat_flags = network_game_info->cheat_flags; is_networked= true; record_game= true; // ZZZ: until players specify their behavior modifiers over the network, // to avoid out-of-sync we must force them all the same. standardize_player_behavior_modifiers(); } #endif // !defined(DISABLE_NETWORKING) break; case _replay_from_file: case _replay: case _demo: switch(user) { case _replay: { FileSpecifier ReplayFile; show_cursor(); // JTP: Hidden one way or another :p success= find_replay_to_use(cheat, ReplayFile); if(success) { success= setup_for_replay_from_file(ReplayFile, get_current_map_checksum()); hide_cursor(); } } break; case _demo: success= setup_replay_from_random_resource(get_current_map_checksum()); break; case _replay_from_file: success= setup_for_replay_from_file(DraggedReplayFile, get_current_map_checksum()); user= _replay; break; default: assert(false); break; } if(success) { uint32 unused1; short recording_version; get_recording_header_data(&number_of_players, &entry.level_number, &unused1, &recording_version, starts, &game_information); if(recording_version > max_handled_recording) { stop_replay(); alert_user(infoError, strERRORS, replayVersionTooNew, 0); success= false; } else { entry.level_name[0] = 0; game_information.game_options |= _overhead_map_is_omniscient; record_game= false; // ZZZ: until films store behavior modifiers, we must require // that they record and playback only with standard modifiers. standardize_player_behavior_modifiers(); } } break; case _single_player: if(cheat) { entry.level_number= get_level_number_from_user(); if(entry.level_number==NONE) success= false; /* Cancelled */ } else { entry.level_number= 0; } // ZZZ: let the user use his behavior modifiers in single-player. restore_custom_player_behavior_modifiers(); entry.level_name[0] = 0; starts[0].identifier = 0; //AS: make things clearer memset(entry.level_name,0,66); construct_single_player_start(starts, &number_of_players); game_information.game_time_remaining= INT32_MAX; game_information.kill_limit = 0; game_information.game_type= _game_of_kill_monsters; game_information.game_options= _burn_items_on_death|_ammo_replenishes|_weapons_replenish|_monsters_replenish; game_information.initial_random_seed= machine_tick_count(); game_information.difficulty_level= get_difficulty_level(); // ZZZ: until film files store player behavior flags, we must require // that all films recorded be made with standard behavior. record_game= is_player_behavior_standard(); break; default: assert(false); break; } if(success) { if(record_game) { set_recording_header_data(number_of_players, entry.level_number, get_current_map_checksum(), default_recording_version, starts, &game_information); start_recording(); } hide_cursor(); /* This has already been done to get to gather/join */ if(can_interface_fade_out()) { interface_fade_out(MAIN_MENU_BASE, true); } /* Try to display the first chapter screen.. */ if (user != _network_player && user != _demo) { FindLevelMovie(entry.level_number); show_movie(entry.level_number); try_and_display_chapter_screen(CHAPTER_SCREEN_BASE + entry.level_number, false, false); } /* Begin the game! */ success= new_game(number_of_players, is_networked, &game_information, starts, &entry); if(success) { start_game(user, false); } else { clean_up_after_failed_game(user == _network_player, record_game, clean_up_on_failure); } } else { /* This means that some weird replay problem happened: */ /* 1) User cancelled */ /* 2) Demos not present */ /* 3) Error... */ /* Either way, we eat them.. */ } return success; } static void start_game( short user, bool changing_level) { /* Change our menus.. */ toggle_menus(true); // LP change: reset screen so that extravision will not be persistent reset_screen(); enter_screen(); // LP: this is in case we are starting underneath a liquid if (!OGL_IsActive() || !(TEST_FLAG(Get_OGL_ConfigureData().Flags,OGL_Flag_Fader))) { set_fade_effect(NONE); SetFadeEffectDelay(TICKS_PER_SECOND/2); } // Screen should already be black! validate_world_window(); draw_interface(); #ifdef PERFORMANCE PerfControl(perf_globals, true); #endif // ZZZ: If it's a netgame, we want prediction; else no. set_prediction_wanted(user == _network_player); game_state.state= _game_in_progress; game_state.current_screen= 0; game_state.phase = MACHINE_TICKS_PER_SECOND; game_state.last_ticks_on_idle= machine_tick_count(); game_state.user= user; game_state.flags= 0; assert((!changing_level&&!get_keyboard_controller_status()) || (changing_level && get_keyboard_controller_status())); if(!changing_level) { set_keyboard_controller_status(true); } } // LP: "static" removed void handle_load_game( void) { FileSpecifier FileToLoad; bool success= false; force_system_colors(); show_cursor(); // JTP: Was hidden by force system colors if(choose_saved_game_to_load(FileToLoad)) { if(load_and_start_game(FileToLoad)) { success= true; } } if(!success) { hide_cursor(); // JTP: Will be shown when fade stops display_main_menu(); } } static void finish_game( bool return_to_main_menu) { set_keyboard_controller_status(false); #ifdef PERFORMANCE PerfControl(perf_globals, false); #endif /* Note that we have to deal with the switch demo state later because */ /* Alain's code calls us at interrupt level 1. (so we defer it) */ assert(game_state.state==_game_in_progress || game_state.state==_switch_demo || game_state.state==_revert_game || game_state.state==_change_level || game_state.state==_begin_display_of_epilogue); toggle_menus(false); stop_fade(); set_fade_effect(NONE); exit_screen(); /* Stop the replay */ switch(game_state.user) { case _single_player: case _network_player: stop_recording(); break; case _demo: case _replay: stop_replay(); break; default: vhalt(csprintf(temporary, "What is user %d?", game_state.user)); break; } /* Fade out! (Pray) */ // should be interface_color_table for valkyrie, but doesn't work. Music::instance()->ClearLevelMusic(); Music::instance()->FadeOut(MACHINE_TICKS_PER_SECOND / 2); full_fade(_cinematic_fade_out, interface_color_table); paint_window_black(); full_fade(_end_cinematic_fade_out, interface_color_table); show_cursor(); leaving_map(); // LP: stop playing the background music if it was present Music::instance()->StopLevelMusic(); /* Get as much memory back as we can. */ free_and_unlock_memory(); // this could call free_map.. unload_all_collections(); SoundManager::instance()->UnloadAllSounds(); #if !defined(DISABLE_NETWORKING) if (game_state.user==_network_player) { if(game_state.current_netgame_allows_microphone) { remove_network_microphone(); } NetUnSync(); // gracefully exit from the game /* Don't update the screen, etc.. */ game_state.state= _displaying_network_game_dialogs; force_system_colors(); display_net_game_stats(); exit_networking(); } #endif // !defined(DISABLE_NETWORKING) if(return_to_main_menu) display_main_menu(); } static void clean_up_after_failed_game(bool inNetgame, bool inRecording, bool inFullCleanup) { /* Stop recording.. */ if(inRecording) { stop_recording(); } /* Show the cursor here on failure. */ show_cursor(); /* The only time we don't clean up is on the replays.. */ if(inFullCleanup) { if (inNetgame) { #if !defined(DISABLE_NETWORKING) if(game_state.current_netgame_allows_microphone) { remove_network_microphone(); } exit_networking(); #endif // !defined(DISABLE_NETWORKING) } else { /* NOTE: The network code is now responsible for displaying its own errors!!!! */ /* Give them the error... */ display_loading_map_error(); } /* Display the main menu on failure.... */ display_main_menu(); } set_game_error(systemError, errNone); } static void handle_network_game( bool gatherer) { #if !defined(DISABLE_NETWORKING) bool successful_gather = false; bool joined_resume_game = false; force_system_colors(); /* Don't update the screen, etc.. */ game_state.state= _displaying_network_game_dialogs; if(gatherer) { successful_gather= network_gather(false); if (successful_gather) successful_gather= NetStart(); } else { int theNetworkJoinResult= network_join(); if (theNetworkJoinResult == kNetworkJoinedNewGame || theNetworkJoinResult == kNetworkJoinedResumeGame) successful_gather= true; if (theNetworkJoinResult == kNetworkJoinedResumeGame) joined_resume_game= true; } if (successful_gather) { if (joined_resume_game) { if (join_networked_resume_game() == false) clean_up_after_failed_game(true /*netgame*/, false /*recording*/, true /*full cleanup*/); } else { begin_game(_network_player, false); } } else { /* We must restore the colors on cancel. */ display_main_menu(); } #else // !defined(DISABLE_NETWORKING) alert_user(infoError, strERRORS, networkNotSupportedForDemo, 0); #endif // !defined(DISABLE_NETWORKING) } static void handle_save_film( void) { force_system_colors(); show_cursor(); // JTP: Hidden by force_system_colors move_replay(); hide_cursor(); // JTP: Will be shown by display_main_menu display_main_menu(); } static void next_game_screen( void) { struct screen_data *data= get_screen_data(game_state.state); stop_interface_fade(); if(++game_state.current_screen>=data->screen_count) { switch(game_state.state) { case _display_main_menu: /* Whoops. didn't get it. */ alert_user(fatalError, strERRORS, outOfMemory, 0); break; case _display_quit_screens: /* Fade out.. */ interface_fade_out(data->screen_base+game_state.current_screen, true); game_state.state= _quit_game; break; default: display_main_menu(); break; } } else { if(game_state.state==_display_intro_screens && game_state.current_screen==INTRO_SCREEN_TO_START_SONG_ON) { Music::instance()->RestartIntroMusic(); } // LP addition: check to see if a picture exists before drawing it. // Otherwise, set the countdown value to zero. short pict_resource_number= data->screen_base+game_state.current_screen; if (images_picture_exists(pict_resource_number)) { game_state.phase= data->duration; game_state.last_ticks_on_idle= machine_tick_count(); display_screen(data->screen_base); } else { game_state.phase= 0; game_state.last_ticks_on_idle= machine_tick_count(); } } } static void display_loading_map_error( void) { short error, type; /* Give them the error... */ error= get_game_error(&type); if(type==gameError) { short string_id; switch(error) { case errServerDied: string_id= serverQuitInCooperativeNetGame; break; case errUnsyncOnLevelChange: string_id= unableToGracefullyChangeLevelsNet; break; case errMapFileNotSet: case errIndexOutOfRange: case errTooManyOpenFiles: case errUnknownWadVersion: case errWadIndexOutOfRange: default: string_id= badReadMapGameError; break; } alert_user(infoError, strERRORS, string_id, error); } else { alert_user(infoError, strERRORS, badReadMapSystemError, error); } set_game_error(systemError, errNone); } static void force_system_colors( void) { if(can_interface_fade_out()) { interface_fade_out(MAIN_MENU_BASE, true); } if(interface_bit_depth==8) { struct color_table *system_colors= build_8bit_system_color_table(); assert_world_color_table(system_colors, (struct color_table *) NULL); delete system_colors; } } static void display_screen( short base_pict_id) { short pict_resource_number= base_pict_id+game_state.current_screen; static bool picture_drawn= false; if (images_picture_exists(pict_resource_number)) { stop_interface_fade(); if(current_picture_clut) { interface_fade_out(pict_resource_number, false); } assert(!current_picture_clut); current_picture_clut= calculate_picture_clut(CLUTSource_Images,pict_resource_number); current_picture_clut_depth= interface_bit_depth; if(current_picture_clut) { /* slam the entire clut to black, now. */ if (interface_bit_depth==8) { assert_world_color_table(current_picture_clut, (struct color_table *) NULL); } full_fade(_start_cinematic_fade_in, current_picture_clut); draw_full_screen_pict_resource_from_images(pict_resource_number); #if defined(MAC_SDL_KLUDGE) draw_full_screen_pict_resource_from_images(pict_resource_number); #endif picture_drawn= true; assert(current_picture_clut); start_interface_fade(_long_cinematic_fade_in, current_picture_clut); } } if(!picture_drawn) { dprintf("Didn't draw: %d;g", pict_resource_number); /* Go for the next one.. */ next_game_screen(); } } static bool point_in_rectangle( short x, short y, screen_rectangle *rect) { bool in_rectangle= false; if(x>=rect->left && xright && y>=rect->top && ybottom) { in_rectangle= true; } return in_rectangle; } #ifndef SDL extern bool MoviePlaying; // interface_macintosh.cpp #endif static void handle_interface_menu_screen_click( short x, short y, bool cheatkeys_down) { short index; screen_rectangle *screen_rect; short xoffset = 0, yoffset = 0; #ifndef SDL if (MoviePlaying) return; // fixes click-through movie bug #endif #ifdef SDL xoffset = (SDL_GetVideoSurface()->w - 640) / 2; yoffset = (SDL_GetVideoSurface()->h - 480) / 2; #endif /* find it.. */ for(index= _new_game_button_rect; indexPlaySoundResource(SoundRsrc); #endif } /* Fade in.... */ assert(current_picture_clut); full_fade(_long_cinematic_fade_in, current_picture_clut); scroll_full_screen_pict_resource_from_scenario(pict_resource_number, text_block); wait_for_click_or_keypress(text_block ? -1 : 10*MACHINE_TICKS_PER_SECOND); /* Fade out! (Pray) */ interface_fade_out(pict_resource_number, false); #if defined(mac) if (channel) SndDisposeChannel(channel, true); #elif defined(SDL) Mixer::instance()->StopSoundResource(); #endif } game_state.state= existing_state; } } #if !defined(DISABLE_NETWORKING) /* * Network microphone handling */ void install_network_microphone( void) { open_network_speaker(); NetAddDistributionFunction(kNewNetworkAudioDistributionTypeID, received_network_audio_proc, true); open_network_microphone(); } void remove_network_microphone( void) { close_network_microphone(); NetRemoveDistributionFunction(kNewNetworkAudioDistributionTypeID); close_network_speaker(); } #endif // !defined(DISABLE_NETWORKING) /* ------------ interface fade code */ /* Be aware that we could try to change bit depths before a fade is completed. */ static void start_interface_fade( short type, struct color_table *original_color_table) { hide_cursor(); assert(!interface_fade_in_progress); animated_color_table= new color_table; obj_copy(*animated_color_table, *original_color_table); if(animated_color_table) { interface_fade_in_progress= true; interface_fade_type= type; explicit_start_fade(type, original_color_table, animated_color_table); } } static void update_interface_fades( void) { bool still_fading= false; if(interface_fade_in_progress) { still_fading= update_fades(); if(!still_fading) { stop_interface_fade(); } } } void stop_interface_fade( void) { if(interface_fade_in_progress) { stop_fade(); interface_fade_in_progress= false; assert(animated_color_table); delete animated_color_table; if (interface_bit_depth==8) { assert_world_color_table(current_picture_clut, (struct color_table *) NULL); } if(game_state.state==_display_main_menu) { /* This isn't a showcursor because of balancing problems (first time through..) */ show_cursor(); } } } /* Called right before we start a game.. */ void interface_fade_out( short pict_resource_number, bool fade_music) { assert(current_picture_clut); if(current_picture_clut) { struct color_table *fadeout_animated_color_table; /* We have to check this because they could go into preferences and change on us, */ /* the evil swine. */ if(current_picture_clut_depth != interface_bit_depth) { delete current_picture_clut; current_picture_clut= calculate_picture_clut(CLUTSource_Images,pict_resource_number); current_picture_clut_depth= interface_bit_depth; } hide_cursor(); fadeout_animated_color_table= new color_table; obj_copy(*fadeout_animated_color_table, *current_picture_clut); if(fade_music) Music::instance()->FadeOut(MACHINE_TICKS_PER_SECOND/2); if (fadeout_animated_color_table) { explicit_start_fade(_cinematic_fade_out, current_picture_clut, fadeout_animated_color_table); while (update_fades()) Music::instance()->Idle(); /* Oops. Founda memory leak.. */ delete fadeout_animated_color_table; } if(fade_music) { while(Music::instance()->Playing()) Music::instance()->Idle(); /* and give up the memory */ Music::instance()->Pause(); } paint_window_black(); full_fade(_end_cinematic_fade_out, current_picture_clut); /* Hopefully we can do this here.. */ delete current_picture_clut; current_picture_clut= NULL; } } static bool can_interface_fade_out( void) { return (current_picture_clut==NULL) ? false : true; } bool interface_fade_finished( void) { return fade_finished(); } void do_preferences(void) { struct screen_mode_data mode = graphics_preferences->screen_mode; handle_preferences(); if (mode.bit_depth != graphics_preferences->screen_mode.bit_depth) { paint_window_black(); initialize_screen(&graphics_preferences->screen_mode, false); /* Re fade in, so that we get the proper colortable loaded.. */ display_main_menu(); } else if (memcmp(&mode, &graphics_preferences->screen_mode, sizeof(struct screen_mode_data))) change_screen_mode(&graphics_preferences->screen_mode, false); } /* * Toggle system hotkeys */ void toggle_menus(bool game_started) { // nothing to do } /* * Update game window */ void update_game_window(void) { switch(get_game_state()) { case _game_in_progress: update_screen_window(); break; case _display_quit_screens: case _display_intro_screens_for_demo: case _display_intro_screens: case _display_chapter_heading: case _display_prologue: case _display_epilogue: case _display_credits: case _display_main_menu: update_interface_display(); break; default: break; } } /* * Exit networking */ void exit_networking(void) { #if !defined(DISABLE_NETWORKING) NetExit(); #endif // !defined(DISABLE_NETWORKING) } /* * Show movie */ extern bool option_nosound; void show_movie(short index) { #ifdef HAVE_SMPEG float PlaybackSize = 2; FileSpecifier IntroMovie; FileSpecifier *File = GetLevelMovie(PlaybackSize); if (!File && index == 0) { if (IntroMovie.SetNameWithPath(getcstr(temporary, strFILENAMES, filenameMOVIE))) File = &IntroMovie; } if (!File) return; SDL_Surface *s = SDL_GetVideoSurface(); #if defined(__APPLE__) && defined(__MACH__) if (!(s->flags & SDL_FULLSCREEN)) SDL_putenv("SDL_VIDEO_YUV_HWACCEL=0"); else SDL_putenv("SDL_VIDEO_YUV_HWACCEL="); #endif { SoundManager::Pause pauseSoundManager; SMPEG_Info info; SMPEG *movie; movie = SMPEG_new(File->GetPath(), &info, option_nosound ? 0 : 1); if (!movie) return; if (!info.has_video) { SMPEG_delete(movie); return; } int width = (int) (info.width * PlaybackSize); int height = (int) (info.height * PlaybackSize); SMPEG_setdisplay(movie, SDL_GetVideoSurface(), NULL, NULL); if (width <= s->w && height <= s->h) { SMPEG_scaleXY(movie, width, height); SMPEG_move(movie, (s->w - width) / 2, (s->h - height) / 2); } bool done = false; SMPEG_play(movie); while (!done && SMPEG_status(movie) == SMPEG_PLAYING) { SDL_Event event; while (SDL_PollEvent(&event) ) { switch (event.type) { case SDL_KEYDOWN: case SDL_MOUSEBUTTONDOWN: done = true; break; default: break; } } SDL_Delay(100); } SMPEG_delete(movie); } #endif // HAVE_SMPEG } size_t should_restore_game_networked() { dialog d; d.add(new w_static_text("RESUME GAME", TITLE_FONT, TITLE_COLOR)); d.add(new w_spacer()); w_toggle* theRestoreAsNetgameToggle = new w_toggle("Resume as", dynamic_world->player_count > 1); theRestoreAsNetgameToggle->set_labels_stringset(kSingleOrNetworkStringSetID); d.add(theRestoreAsNetgameToggle); d.add(new w_spacer()); d.add(new w_spacer()); d.add(new w_left_button("RESUME", dialog_ok, &d)); d.add(new w_right_button("CANCEL", dialog_cancel, &d)); // We return -1 (NONE) for "cancel", 0 for "not networked", and 1 for "networked". size_t theResult; if(d.run() == 0) { theResult = theRestoreAsNetgameToggle->get_selection(); } else { theResult = UNONE; } return theResult; }