// ============================== IMPLEMENTATIONS =============================
void dc_do_command(SCP_string *cmd_str)
{
	/**
	 * Grab the first word from the cmd_str
	 *  If it is not a literal, ignore it "Invalid keyword: %s"
	 *  Search for the command...
	 *      Compare the word against valid commands
	 *      If command not found, ignore it "Invalid or unknown command: %s"\
	 *  Process the command...
	 *      Call the function to process the command (the rest of the command line is in the parser)
	 *          Function takes care of long_help and status depending on the mode.
	 */
	SCP_string command;
	extern SCP_vector<debug_command*> dc_commands;	// z64: I don't like this extern here, at all. Nope nope nope.

	if (cmd_str->empty()) {
		return;
	}

	dc_parse_init(*cmd_str);

	dc_stuff_string_white(command);		// Grab the first token, presumably this is a command

	SCP_vector<debug_command*>::iterator it = find_if(dc_commands.begin(), dc_commands.end(), is_dcmd(command.c_str()));

	if (it == dc_commands.end()) {
		dc_printf("Command not found: '%s'\n", command.c_str());
		return;
	} // Else, we found our command

	try {
		(*it)->func();	// Run the command!
	
	} catch (errParseString err) {
		dc_printf("Require string(s) not found: \n");
		for (int i = 0; i < err.expected_tokens.size(); ++i) {
			dc_printf("%i: %s\n", err.expected_tokens[i].c_str());
		}

		dc_printf("Found '%s' instead\n", err.found_token.c_str());
	
	} catch (errParse err) {
		dc_printf("Invalid argument. Expected %s, found '%s'\n", token_str[err.expected_type], err.found_token.c_str());

	}

	// dc_maybe_stuff_string is vulnerable to overflow. Once the errParseOverflow throw class (or w/e) gets
	// implemented, this last command should be put into its own try{} catch{} block.
	if (dc_maybe_stuff_string(command)) {
		dc_printf( "Ignoring the unused command line tail '%s'\n", command.c_str() );
	}
}
int gamesnd_get_by_iface_name(const char* name)
{
	Assert( Snds_iface.size() <= INT_MAX );
	Assert( Snds_iface.size() == Snds_iface_handle.size() );

	int index = gamesnd_lookup_name(name, Snds_iface);

	if (index < 0)
	{
		int i = 0;
		for(SCP_vector<game_snd>::iterator snd = Snds_iface.begin(); snd != Snds_iface.end(); ++snd)
		{
			char *p = strrchr( snd->filename, '.' );
			if(p == NULL)
			{
				if(!stricmp(snd->filename, name))
				{
					index = i;
					break;
				}
			}
			else if(!strnicmp(snd->filename, name, p-snd->filename))
			{
				index = i;
				break;
			}

			i++;
		}
	}

	return index;
}
/*
 * Update any uninitialized EnhancedSoundData in Snds
  * with hardcoded defaults for retail.
 */
void gamesnd_add_retail_default_enhanced_sound_data()
{
	int i = 0;

	for (SCP_vector<game_snd>::iterator it = Snds.begin(), end = Snds.end(); it != end; ++it, ++i)
	{
		if (it->enhanced_sound_data.priority== SND_ENHANCED_PRIORITY_INVALID)
		{
			if (i < NUM_RETAIL_GAMEPLAY_SOUNDS)
			{
				it->enhanced_sound_data.priority= Default_sound_priorities[i].priority;
			}
			else
			{
				it->enhanced_sound_data.priority= default_enhanced_sound_data.priority;
			}
		}

		if (it->enhanced_sound_data.limit < 1)
		{
			if (i < NUM_RETAIL_GAMEPLAY_SOUNDS)
			{
				it->enhanced_sound_data.limit = Default_sound_priorities[i].limit;
			}
			else
			{
				it->enhanced_sound_data.limit= default_enhanced_sound_data.limit;
			}
		}
	}
}
void waypoint_add_list(const char *name, SCP_vector<vec3d> &vec_list)
{
	Assert(name != NULL);

	if (find_matching_waypoint_list(name) != NULL)
	{
		Warning(LOCATION, "Waypoint list '%s' already exists in this mission!  Not adding the new list...", name);
		return;
	}

	waypoint_list new_list(name);
	Waypoint_lists.push_back(new_list);
	waypoint_list *wp_list = &Waypoint_lists.back();

	wp_list->get_waypoints().reserve(vec_list.size());
	SCP_vector<vec3d>::iterator ii;
	for (ii = vec_list.begin(); ii != vec_list.end(); ++ii)
	{
		waypoint new_waypoint(&(*ii), wp_list);
		wp_list->get_waypoints().push_back(new_waypoint);
	}

	// so that masking in the other function works
	// though if you actually hit this Assert, you have other problems
	Assert(wp_list->get_waypoints().size() <= 0xffff);
}
// marks a cutscene as viewable
void cutscene_mark_viewable(char *filename)
{
	char cut_file[MAX_FILENAME_LEN];
	char file[MAX_FILENAME_LEN];

	Assert(filename!=NULL);

	// strip off extension
	strcpy_s( file, filename );
	char *p = strchr( file, '.' );
	if ( p ) {
		*p = 0;
	}

	// change to lower case
	strlwr(file);
	int i = 0;
	for (SCP_vector<cutscene_info>::iterator cut = Cutscenes.begin(); cut != Cutscenes.end(); ++cut) {
		// change the cutscene file name to lower case
		strcpy_s(cut_file, cut->filename);
		strlwr(cut_file);

		// see if the stripped filename matches the cutscene filename
		if ( strstr(cut_file, file) != NULL ) {
			cut->viewable = true;
			return;
		}
		i++;
	}
}
void cutscene_close()
{
	for(SCP_vector<cutscene_info>::iterator cut = Cutscenes.begin(); cut != Cutscenes.end(); ++cut)
		if(cut->description != NULL) {
			vm_free(cut->description);
			cut->description = NULL;
		}
}
Exemple #7
0
void subtitles_do_frame_post_shaded(float frametime)
{
	SCP_vector<subtitle>::iterator sub;
	for(sub = Subtitles.begin(); sub != Subtitles.end(); ++sub)
	{
		if ( sub->is_post_shaded( ) )
			sub->do_frame(frametime);
	}
}
// only call from game_shutdown()!!!
void particle_close()
{
	for (SCP_vector<particle*>::iterator p = Particles.begin(); p != Particles.end(); ++p)
	{
		(*p)->signature = 0;
		delete *p;
	}
	Particles.clear();
}
void cam_close()
{
	//Set Current_camera to nothing
	Current_camera = camid();
	for (auto ii = Cameras.begin(); ii != Cameras.end(); ++ii) {
		delete * ii;
	}
	Cameras.clear();
}
Exemple #10
0
int batch_get_size()
{
    int n_to_render = 0;
    SCP_vector<batch_item>::iterator bi;

    for (bi = geometry_map.begin(); bi != geometry_map.end(); ++bi) {
        n_to_render += bi->batch.need_to_render();
    }

    for (bi = distortion_map.begin(); bi != distortion_map.end(); ++bi) {
        if ( bi->laser )
            continue;

        n_to_render += bi->batch.need_to_render();
    }

    return n_to_render * 3;
}
/**
 * Unload the ingame sounds from memory
 */
void gamesnd_unload_gameplay_sounds()
{
	Assert( Snds.size() <= INT_MAX );
	for (SCP_vector<game_snd>::iterator gs = Snds.begin(); gs != Snds.end(); ++gs) {
		if ( gs->id != -1 ) {
			snd_unload( gs->id );
			gs->id = -1;
		}
	}
}
void gr_opengl_recompile_all_shaders(const std::function<void(size_t, size_t)>& progress_callback)
{
	for (auto sdr = GL_shader.begin(); sdr != GL_shader.end(); ++sdr)
	{
		if (progress_callback)
			progress_callback(std::distance(GL_shader.begin(), sdr), GL_shader.size());
		sdr->program.reset();
		opengl_compile_shader_actual(sdr->shader, sdr->flags, *sdr);
	}
}
// function to return 0 based index of which CD a particular movie is on
// returns -1 on failure.
int cutscenes_get_cd_num( char *filename )
{
	for (SCP_vector<cutscene_info>::iterator cut = Cutscenes.begin(); cut != Cutscenes.end(); ++cut) {
		if ( !stricmp(cut->filename, filename) ) {
			return (cut->cd - 1);
		}
	}

	return -1;
}
/**
 * Unload the interface sounds from memory
 */
void gamesnd_unload_interface_sounds()
{
	Assert( Snds_iface.size() < INT_MAX );
	for (SCP_vector<game_snd>::iterator si = Snds_iface.begin(); si != Snds_iface.end(); ++si) {
		if ( si->id != -1 ) {
			snd_unload( si->id );
			si->id = -1;
			si->id_sig = -1;
		}
	}
}
/**
 * Load the interface sounds into memory
 */
void gamesnd_load_interface_sounds()
{
	if ( !Sound_enabled )
		return;

	Assert( Snds_iface.size() < INT_MAX );
	for (SCP_vector<game_snd>::iterator si = Snds_iface.begin(); si != Snds_iface.end(); ++si) {
		if ( si->filename[0] != 0 && strnicmp(si->filename, NOX("none.wav"), 4) ) {
			si->id = snd_load(&(*si));
		}
	}
}
static void find_capture_device(OpenALInformation* info)
{
	const char *user_device = os_config_read_string( "Sound", "CaptureDevice", NULL );
	const char *default_device = info->default_capture_device.c_str();

	// in case they are the same, we only want to test it once
	if ( (user_device && default_device) && !strcmp(user_device, default_device) ) {
		user_device = NULL;
	}

	for (auto& device : info->capture_devices) {
		OALdevice new_device(device.c_str());

		if (user_device && !strcmp(device.c_str(), user_device)) {
			new_device.type = OAL_DEVICE_USER;
		} else if (default_device && !strcmp(device.c_str(), default_device)) {
			new_device.type = OAL_DEVICE_DEFAULT;
		}

		CaptureDevices.push_back( new_device );
	}

	if ( CaptureDevices.empty() ) {
		return;
	}

	std::sort( CaptureDevices.begin(), CaptureDevices.end(), openal_device_sort_func );


	// for each device that we have available, try and figure out which to use
	for (size_t idx = 0; idx < CaptureDevices.size(); idx++) {
		const ALCchar *device_name = CaptureDevices[idx].device_name.c_str();

		ALCdevice *device = alcCaptureOpenDevice(device_name, 22050, AL_FORMAT_MONO8, 22050 * 2);

		if (device == NULL) {
			continue;
		}

		if (alcGetError(device) != ALC_NO_ERROR) {
			alcCaptureCloseDevice(device);
			continue;
		}

		// ok, we should be good with this one
		Capture_device = CaptureDevices[idx].device_name;

		alcCaptureCloseDevice(device);

		break;
	}
}
// kill all active particles
void particle_kill_all()
{
	// kill all active particles
	Num_particles = 0;
	Num_particles_hwm = 0;

	for (SCP_vector<particle*>::iterator p = Particles.begin(); p != Particles.end(); ++p)
	{
		(*p)->signature = 0;
		delete *p;
	}
	Particles.clear();
}
Exemple #18
0
void batch_load_buffer_distortion_map_bitmaps(effect_vertex* buffer, int *n_verts)
{
    for (SCP_vector<batch_item>::iterator bi = distortion_map.begin(); bi != distortion_map.end(); ++bi) {

        if ( bi->laser )
            continue;

        if ( !bi->batch.need_to_render() )
            continue;

        Assert( bi->texture >= 0 );
        bi->batch.load_buffer(buffer, n_verts);
    }
}
Exemple #19
0
void os_poll()
{
	// Replay the buffered events
	auto end = buffered_events.end();
	for (auto it = buffered_events.begin(); it != end; ++it) {
		handle_sdl_event(*it);
	}
	buffered_events.clear();

	SDL_Event event;

	while (SDL_PollEvent(&event)) {
		handle_sdl_event(event);
	}
}
/**
 * Load the ingame sounds into memory
 */
void gamesnd_load_gameplay_sounds()
{
	if ( !Sound_enabled )
		return;

	Assert( Snds.size() <= INT_MAX );
	for (SCP_vector<game_snd>::iterator gs = Snds.begin(); gs != Snds.end(); ++gs) {
		if ( gs->filename[0] != 0 && strnicmp(gs->filename, NOX("none.wav"), 4) ) {
			if ( !gs->preload ) { // don't try to load anything that's already preloaded
				game_busy( NOX("** preloading gameplay sounds **") );		// Animate loading cursor... does nothing if loading screen not active.
				gs->id = snd_load(&(*gs));
			}
		}
	}
}
void cutscenes_screen_init()
{
	int i;
	ui_button_info *b;

	Ui_window.create(0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled, 0);
	Ui_window.set_mask_bmap(Cutscene_mask_name[gr_screen.res]);

	for (i=0; i<NUM_BUTTONS; i++) {
		b = &Buttons[gr_screen.res][i];

		b->button.create(&Ui_window, "", b->x, b->y, 60, 30, (i < 2), 1);
		// set up callback for when a mouse first goes over a button
		b->button.set_highlight_action(common_play_highlight_sound);
		b->button.set_bmaps(b->filename);
		b->button.link_hotspot(b->hotspot);
	}

	// add xstrs
	for(i=0; i<NUM_CUTSCENE_TEXT; i++){
		Ui_window.add_XSTR(&Cutscene_text[gr_screen.res][i]);
	}

	Buttons[gr_screen.res][EXIT_BUTTON].button.set_hotkey(KEY_CTRLED | KEY_ENTER);
	Buttons[gr_screen.res][SCROLL_UP_BUTTON].button.set_hotkey(KEY_PAGEUP);
	Buttons[gr_screen.res][SCROLL_DOWN_BUTTON].button.set_hotkey(KEY_PAGEDOWN);	

	List_region.create(&Ui_window, "", Cutscene_list_coords[gr_screen.res][0], Cutscene_list_coords[gr_screen.res][1], Cutscene_list_coords[gr_screen.res][2], Cutscene_list_coords[gr_screen.res][3], 0, 1);
	List_region.hide();

	// set up hotkeys for buttons so we draw the correct animation frame when a key is pressed
	Buttons[gr_screen.res][SCROLL_UP_BUTTON].button.set_hotkey(KEY_PAGEUP);
	Buttons[gr_screen.res][SCROLL_DOWN_BUTTON].button.set_hotkey(KEY_PAGEDOWN);

	Background_bitmap = bm_load(Cutscene_bitmap_name[gr_screen.res]);
	Scroll_offset = Selected_line = 0;
	Description_index = -1;

    Cutscene_list.clear();
	
	int u = 0;
	for (SCP_vector<cutscene_info>::iterator cut = Cutscenes.begin(); cut != Cutscenes.end(); ++cut, u++) {
		if ( (*cut).viewable ) {
			Cutscene_list.push_back(u);
		}
	}
}
Exemple #22
0
void batch_render_lasers(bool stream_buffer)
{
    for (SCP_vector<batch_item>::iterator bi = geometry_map.begin(); bi != geometry_map.end(); ++bi) {

        if ( !bi->laser )
            continue;

        if ( !bi->batch.need_to_render() )
            continue;

        Assert( bi->texture >= 0 );
        gr_set_bitmap(bi->texture, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 0.99999f);
        if ( stream_buffer ) {
            bi->batch.render_buffer(TMAP_FLAG_TEXTURED | TMAP_FLAG_XPARENT | TMAP_HTL_3D_UNLIT | TMAP_FLAG_RGB | TMAP_FLAG_GOURAUD | TMAP_FLAG_CORRECT);
        } else {
            bi->batch.render(TMAP_FLAG_TEXTURED | TMAP_FLAG_XPARENT | TMAP_HTL_3D_UNLIT | TMAP_FLAG_RGB | TMAP_FLAG_GOURAUD | TMAP_FLAG_CORRECT);
        }
    }
}
Exemple #23
0
void batch_render_geometry_map_bitmaps(bool stream_buffer)
{
    for (SCP_vector<batch_item>::iterator bi = geometry_map.begin(); bi != geometry_map.end(); ++bi) {

        if ( bi->laser )
            continue;

        if ( !bi->batch.need_to_render() )
            continue;

        Assert( bi->texture >= 0 );
        gr_set_bitmap(bi->texture, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, bi->alpha);
        if ( stream_buffer ) {
            bi->batch.render_buffer(bi->tmap_flags);
        } else {
            bi->batch.render( bi->tmap_flags);
        }
    }
}
/**
 * Pass a GLSL shader source to OpenGL and compile it into a usable shader object.
 * Prints compilation errors (if any) to the log.
 * Note that this will only compile shaders into objects, linking them into executables happens later
 *
 * @param shader_source		GLSL sourcecode for the shader
 * @param shader_type		OpenGL ID for the type of shader being used, like GL_FRAGMENT_SHADER_ARB, GL_VERTEX_SHADER_ARB
 * @return 					OpenGL handle for the compiled shader object
 */
GLhandleARB opengl_shader_compile_object(const SCP_vector<SCP_string>& shader_source, GLenum shader_type)
{
	GLhandleARB shader_object = 0;
	GLint status = 0;

	SCP_vector<const GLcharARB*> sources;
	sources.reserve(shader_source.size());
	for (auto it = shader_source.begin(); it != shader_source.end(); ++it) {
		sources.push_back(it->c_str());
	}

	shader_object = vglCreateShaderObjectARB(shader_type);

	vglShaderSourceARB(shader_object, sources.size(), &sources[0], NULL);
	vglCompileShaderARB(shader_object);

	// check if the compile was successful
	vglGetObjectParameterivARB(shader_object, GL_OBJECT_COMPILE_STATUS_ARB, &status);

	opengl_shader_check_info_log(shader_object);

	// we failed, bail out now...
	if (status == 0) {
		// basic error check
		mprintf(("%s shader failed to compile:\n%s\n", (shader_type == GL_VERTEX_SHADER_ARB) ? "Vertex" : ((shader_type == GL_GEOMETRY_SHADER_EXT) ? "Geometry" : "Fragment"), GLshader_info_log));

		// this really shouldn't exist, but just in case
		if (shader_object) {
			vglDeleteObjectARB(shader_object);
		}

		return 0;
	}

	// we succeeded, maybe output warnings too
	if (strlen(GLshader_info_log) > 5) {
		nprintf(("SHADER-DEBUG", "%s shader compiled with warnings:\n%s\n", (shader_type == GL_VERTEX_SHADER_ARB) ? "Vertex" : ((shader_type == GL_GEOMETRY_SHADER_EXT) ? "Geometry" : "Fragment"), GLshader_info_log));
	}

	return shader_object;
}
void dc_init(void)
{
	extern SCP_vector<debug_command*> dc_commands;

	if (debug_inited) {
		return;
	}

	debug_inited = TRUE;

	// Sort debug_commands
	std::sort(dc_commands.begin(), dc_commands.end(), dcmd_less);

	// Init window settings
	dc_font = FONT1;
	row_height = (Current_font->h) * 1.5;	// Row/Line height, in pixels
	col_width = Current_font->w;			// Col/Character width, in pixels
	dc_scroll_x = 0;
	dc_scroll_y = 0;
	DCOLS = (gr_screen.max_w / col_width) - 1;	// Subtract as needed. Windowed mode has some quirks with the resolution
	DROWS = (gr_screen.max_h / row_height) - 2;
	DBCOLS = DCOLS;
	DBROWS = 2 * DROWS;

	// Init History
	dc_history.clear();
	dc_history.push_back("");
	last_oldcommand = dc_history.begin();

	// Init buffers
	dc_buffer.clear();
	dc_buffer.push_back("");
	
	dc_command_buf.reserve(MAX_CLI_LEN);
	dc_command_buf.clear();

	sprintf(dc_title, "FreeSpace Open v%i.%i.%i", FS_VERSION_MAJOR, FS_VERSION_MINOR, FS_VERSION_BUILD);
	dc_printf("Debug console started.\n" );
}
void fs2netd_update_ban_list()
{
	int rc = 0;

	if ( !Logged_in ) {
		return;
	}

	// destroy the file prior to updating
	cf_delete( "banlist.cfg", CF_TYPE_DATA );

	do_full_packet = true;

	In_process = true;

	if (Is_standalone) {
		do { rc = fs2netd_update_ban_list_do(); } while (!rc);
	} else {
		rc = popup_till_condition(fs2netd_update_ban_list_do, XSTR("&Cancel", 779), XSTR("Requesting IP ban list", 1587));
	}

	In_process = false;
	Local_timeout = -1;

	if ( !FS2NetD_ban_list.empty() ) {
		CFILE *banlist_cfg = cfopen("banlist.cfg", "wt", CFILE_NORMAL, CF_TYPE_DATA);

		if (banlist_cfg != NULL) {
			for (SCP_vector<SCP_string>::iterator bl = FS2NetD_ban_list.begin(); bl != FS2NetD_ban_list.end(); ++bl) {
				cfputs( const_cast<char*>(bl->c_str()), banlist_cfg );
			}

			cfclose(banlist_cfg);
		}
	}

	FS2NetD_ban_list.clear();
}
/**
 * Function to search for a given game_snd with the specified name
 * in the passed vector
 *
 * @param name Name to search for
 * @param sounds Vector to seach in
 *
 */
int gamesnd_lookup_name(const char* name, const SCP_vector<game_snd>& sounds)
{
	// if we get passed -1, don't bother trying to look it up.
	if (name == NULL || *name == 0 || !strcmp(name, "-1"))
	{
		return -1;
	}

	Assert( sounds.size() <= INT_MAX );

	int i = 0;

	for(SCP_vector<game_snd>::const_iterator snd = sounds.begin(); snd != sounds.end(); ++snd)
	{
		if (!snd->name.compare(name))
		{
			return i;
		}
		i++;
	}

	return -1;
}
static void handle_includes_impl(SCP_vector<SCP_string>& include_stack,
								 SCP_stringstream& output,
								 int& include_counter,
								 const SCP_string& filename,
								 const SCP_string& original) {
	include_stack.emplace_back(filename);
	auto current_source_number = include_counter + 1;

	const char* INCLUDE_STRING = "#include";
	SCP_stringstream input(original);

	int line_num = 1;
	for (SCP_string line; std::getline(input, line);) {
		auto include_start = line.find(INCLUDE_STRING);
		if (include_start != SCP_string::npos) {
			auto first_quote = line.find('"', include_start + strlen(INCLUDE_STRING));
			auto second_quote = line.find('"', first_quote + 1);

			if (first_quote == SCP_string::npos || second_quote == SCP_string::npos) {
				Error(LOCATION,
					  "Shader %s:%d: Malformed include line. Could not find both quote charaters.",
					  filename.c_str(),
					  line_num);
			}

			auto file_name = line.substr(first_quote + 1, second_quote - first_quote - 1);
			auto existing_name = std::find_if(include_stack.begin(), include_stack.end(), [&file_name](const SCP_string& str) {
				return str == file_name;
			});
			if (existing_name != include_stack.end()) {
				SCP_stringstream stack_string;
				for (auto& name : include_stack) {
					stack_string << "\t" << name << "\n";
				}

				Error(LOCATION,
					  "Shader %s:%d: Detected cyclic include! Previous includes (top level file first):\n%s",
					  filename.c_str(),
					  line_num,
					  stack_string.str().c_str());
			}

			++include_counter;
			// The second parameter defines which source string we are currently working with. We keep track of how many
			// excludes have been in the file so far to specify this
			output << "#line 1 " << include_counter + 1 << "\n";

			handle_includes_impl(include_stack,
								 output,
								 include_counter,
								 file_name,
								 opengl_load_shader(file_name.c_str()));

			// We are done with the include file so now we can return to the original file
			output << "#line " << line_num + 1 << " " << current_source_number << "\n";
		} else {
			output << line << "\n";
		}

		++line_num;
	}

	include_stack.pop_back();
}
void credits_do_frame(float frametime)
{
	GR_DEBUG_SCOPE("Credits do frame");

	int i, k, next, percent, bm1, bm2;
	int bx1, by1, bw1, bh1;
	int bx2, by2, bw2, bh2;

	// Use this id to trigger the start of music playing on the credits screen
	if ( timestamp_elapsed(Credits_music_begin_timestamp) ) {
		Credits_music_begin_timestamp = 0;
		credits_start_music();
	}

	k = Ui_window.process();
	switch (k) {
	case KEY_ESC:
		gameseq_post_event(GS_EVENT_MAIN_MENU);
		key_flush();
		break;

	case KEY_CTRLED | KEY_UP:
	case KEY_SHIFTED | KEY_TAB:
		if ( !(Player->flags & PLAYER_FLAGS_IS_MULTI) ) {
			credits_screen_button_pressed(CUTSCENES_BUTTON);
			break;
		}
		// else, react like tab key.

	case KEY_CTRLED | KEY_DOWN:
	case KEY_TAB:
		credits_screen_button_pressed(TECH_DATABASE_BUTTON);
		break;

	default:
		break;
	} // end switch

	for (i=0; i<NUM_BUTTONS; i++){
		if (Buttons[i][gr_screen.res].button.pressed()){
			if (credits_screen_button_pressed(i)){
				return;
			}
		}
	}

	gr_reset_clip();	
	GR_MAYBE_CLEAR_RES(Background_bitmap);
	if (Background_bitmap >= 0) {
		gr_set_bitmap(Background_bitmap);
		gr_bitmap(0, 0, GR_RESIZE_MENU);
	} 

	percent = (int) (100.0f - (Credits_artwork_display_time - Credits_counter) * 100.0f / Credits_artwork_fade_time);
	if (percent < 0){
		percent = 0;
	}

	next = Credits_artwork_index + 1;
	if (next >= Credits_num_images){
		next = 0;
	}

	if (Credits_bmps[Credits_artwork_index] < 0) {
		char buf[40];

		if (gr_screen.res == GR_1024) {
			sprintf(buf, NOX("2_CrIm%.2d"), Credits_artwork_index);
		} else {
			sprintf(buf, NOX("CrIm%.2d"), Credits_artwork_index);
		}
		Credits_bmps[Credits_artwork_index] = bm_load(buf);
	}

	if (Credits_bmps[next] < 0) {
		char buf[40];

		if (gr_screen.res == GR_1024) {
			sprintf(buf, NOX("2_CrIm%.2d"), next);
		} else {
			sprintf(buf, NOX("CrIm%.2d"), next);
		}
		Credits_bmps[next] = bm_load(buf);
	}

	bm1 = Credits_bmps[Credits_artwork_index];
	bm2 = Credits_bmps[next];

	if((bm1 != -1) && (bm2 != -1)){
		GR_DEBUG_SCOPE("Render credits bitmap");

		Assert(percent >= 0 && percent <= 100);

		// get width and height
		bm_get_info(bm1, &bw1, &bh1, NULL, NULL, NULL);	
		bm_get_info(bm2, &bw2, &bh2, NULL, NULL, NULL);	
	
		// determine where to draw the coords
		bx1 = Credits_image_coords[gr_screen.res][CREDITS_X_COORD] + ((Credits_image_coords[gr_screen.res][CREDITS_W_COORD] - bw1)/2);
		by1 = Credits_image_coords[gr_screen.res][CREDITS_Y_COORD] + ((Credits_image_coords[gr_screen.res][CREDITS_H_COORD] - bh1)/2);
		bx2 = Credits_image_coords[gr_screen.res][CREDITS_X_COORD] + ((Credits_image_coords[gr_screen.res][CREDITS_W_COORD] - bw2)/2);
		by2 = Credits_image_coords[gr_screen.res][CREDITS_Y_COORD] + ((Credits_image_coords[gr_screen.res][CREDITS_H_COORD] - bh2)/2);

		auto alpha = (float)percent / 100.0f;

		gr_set_bitmap(bm1, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 1.0f - alpha);
		gr_bitmap(bx1, by1, GR_RESIZE_MENU);

		gr_set_bitmap(bm2, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, alpha);
		gr_bitmap(bx2, by2, GR_RESIZE_MENU);
	}

	Ui_window.draw();

	for (i=TECH_DATABASE_BUTTON; i<=CREDITS_BUTTON; i++){
		if (Buttons[i][gr_screen.res].button.button_down()){
			break;
		}
	}

	if (i > CREDITS_BUTTON){
		Buttons[CREDITS_BUTTON][gr_screen.res].button.draw_forced(2);
	}

	gr_set_clip(Credits_text_coords[gr_screen.res][CREDITS_X_COORD], Credits_text_coords[gr_screen.res][CREDITS_Y_COORD], Credits_text_coords[gr_screen.res][CREDITS_W_COORD], Credits_text_coords[gr_screen.res][CREDITS_H_COORD], GR_RESIZE_MENU);
	font::set_font(font::FONT1);
	gr_set_color_fast(&Color_normal);
	
	int y_offset = 0;
	for (SCP_vector<SCP_string>::iterator iter = Credit_text_parts.begin(); iter != Credit_text_parts.end(); ++iter)
	{
		size_t currentPos = 0;
		size_t lineEnd;
		do
		{
			int height;
			int width;
			lineEnd = iter->find('\n', currentPos);

			auto length = lineEnd - currentPos;
			if (lineEnd == SCP_string::npos)
			{
				length = std::numeric_limits<size_t>::max();
			}

			gr_get_string_size(&width, &height, iter->c_str() + currentPos, static_cast<int>(length));
			// Check if the text part is actually visible
			if (Credit_position + y_offset + height > 0.0f)
			{
				float x = static_cast<float>((gr_screen.clip_width_unscaled - width) / 2);
				gr_string(x, Credit_position + y_offset, iter->c_str() + currentPos, GR_RESIZE_MENU, static_cast<int>(length));
			}

			y_offset += height;
			currentPos = lineEnd + 1;
		} while (lineEnd < iter->length() && lineEnd != SCP_string::npos);
	}

	int temp_time;
	temp_time = timer_get_milliseconds();

	Credits_frametime = temp_time - Credits_last_time;
	Credits_last_time = temp_time;
	timestamp_inc(i2f(Credits_frametime) / TIMESTAMP_FREQUENCY);

	float fl_frametime = i2fl(Credits_frametime) / 1000.f;
	if (keyd_pressed[KEY_LSHIFT]) {
		Credit_position -= fl_frametime * Credits_scroll_rate * 4.0f;
	} else {
		Credit_position -= fl_frametime * Credits_scroll_rate;
	}

	if (Credit_position < Credit_stop_pos){
		Credit_position = Credit_start_pos;
	}

	Credits_counter += fl_frametime;
	while (Credits_counter >= Credits_artwork_display_time) {
		Credits_counter -= Credits_artwork_display_time;
		Credits_artwork_index = next;
	}

	gr_flip();
}
void credits_init()
{
	int i;
	credits_screen_buttons *b;

	// pre-initialize
	Credits_num_images = DEFAULT_NUM_IMAGES;
	Credits_artwork_index = -1;

	// this is moved up here so we can override it if desired
	strcpy_s(Credits_music_name, "Cinema");

	// parse credits early so as to set up any overrides (for music and such)
	Credits_parsed = false;
	credits_parse();

	// we could conceivably have specified a number of images but not an index,
	// so if that's the case, set the value here
	if (Credits_artwork_index < 0) {
		Credits_artwork_index = rand() % Credits_num_images;
	}

	int credits_spooled_music_index = event_music_get_spooled_music_index(Credits_music_name);	
	if(credits_spooled_music_index != -1){
		char *credits_wavfile_name = Spooled_music[credits_spooled_music_index].filename;		
		if(credits_wavfile_name != NULL){
			credits_load_music(credits_wavfile_name);
		}
	}

	// Use this id to trigger the start of music playing on the briefing screen
	Credits_music_begin_timestamp = timestamp(Credits_music_delay);

	Credits_frametime = 0;
	Credits_last_time = timer_get_milliseconds();
	
	if (!Credits_parsed)
	{
		Credit_text_parts.push_back(SCP_string("No credits available.\n"));
	}
	else
	{
		switch (SCP_credits_position)
		{
			case START:
				Credit_text_parts.insert(Credit_text_parts.begin(), fs2_open_credit_text);
				break;

			case END:
				Credit_text_parts.push_back(fs2_open_credit_text);
				break;

			default:
				Error(LOCATION, "Unimplemented credits position %d. Get a coder!", (int) SCP_credits_position);
				break;
		}
	}

	int ch;
	SCP_vector<SCP_string>::iterator iter;

	for (iter = Credit_text_parts.begin(); iter != Credit_text_parts.end(); ++iter)
	{
		for (SCP_string::iterator ii = iter->begin(); ii != iter->end(); ++ii)
		{
			ch = *ii;
			switch (ch)
			{
				case -4:
					ch = 129;
					break;

				case -28:
					ch = 132;
					break;

				case -10:
					ch = 148;
					break;

				case -23:
					ch = 130;
					break;

				case -30:
					ch = 131;
					break;

				case -25:
					ch = 135;
					break;

				case -21:
					ch = 137;
					break;

				case -24:
					ch = 138;
					break;

				case -17:
					ch = 139;
					break;

				case -18:
					ch = 140;
					break;

				case -60:
					ch = 142;
					break;

				case -55:
					ch = 144;
					break;

				case -12:
					ch = 147;
					break;

				case -14:
					ch = 149;
					break;

				case -5:
					ch = 150;
					break;

				case -7:
					ch = 151;
					break;

				case -42:
					ch = 153;
					break;

				case -36:
					ch = 154;
					break;

				case -31:
					ch = 160;
					break;

				case -19:
					ch = 161;
					break;

				case -13:
					ch = 162;
					break;

				case -6:
					ch = 163;
					break;

				case -32:
					ch = 133;
					break;

				case -22:
					ch = 136;
					break;

				case -20:
					ch = 141;
					break;
			}

			*ii = (char) ch;
		}
	}

	int temp_h;
	int h = 0;

	for (iter = Credit_text_parts.begin(); iter != Credit_text_parts.end(); ++iter)
	{
		gr_get_string_size(NULL, &temp_h, iter->c_str(), (int)iter->length());

		h = h + temp_h;
	}

	Credit_start_pos = i2fl(Credits_text_coords[gr_screen.res][CREDITS_H_COORD]);
	Credit_stop_pos = -i2fl(h);
	Credit_position = Credit_start_pos;

	Ui_window.create(0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled, 0);
	Ui_window.set_mask_bmap(Credits_bitmap_mask_fname[gr_screen.res]);
	common_set_interface_palette("InterfacePalette");  // set the interface palette

	for (i=0; i<NUM_BUTTONS; i++) {
		b = &Buttons[i][gr_screen.res];

		b->button.create(&Ui_window, "", b->x, b->y, 60, 30, (i < 2), 1);
		// set up callback for when a mouse first goes over a button
		b->button.set_highlight_action(common_play_highlight_sound);
		b->button.set_bmaps(b->filename);
		b->button.link_hotspot(b->hotspot);
	}

	// add some text
	Ui_window.add_XSTR("Technical Database", 1055, Buttons[TECH_DATABASE_BUTTON][gr_screen.res].xt,  Buttons[TECH_DATABASE_BUTTON][gr_screen.res].yt, &Buttons[TECH_DATABASE_BUTTON][gr_screen.res].button, UI_XSTR_COLOR_GREEN);
	Ui_window.add_XSTR("Mission Simulator", 1056, Buttons[SIMULATOR_BUTTON][gr_screen.res].xt,  Buttons[SIMULATOR_BUTTON][gr_screen.res].yt, &Buttons[SIMULATOR_BUTTON][gr_screen.res].button, UI_XSTR_COLOR_GREEN);
	Ui_window.add_XSTR("Cutscenes", 1057, Buttons[CUTSCENES_BUTTON][gr_screen.res].xt,  Buttons[CUTSCENES_BUTTON][gr_screen.res].yt, &Buttons[CUTSCENES_BUTTON][gr_screen.res].button, UI_XSTR_COLOR_GREEN);
	Ui_window.add_XSTR("Credits", 1058, Buttons[CREDITS_BUTTON][gr_screen.res].xt,  Buttons[CREDITS_BUTTON][gr_screen.res].yt, &Buttons[CREDITS_BUTTON][gr_screen.res].button, UI_XSTR_COLOR_GREEN);
	Ui_window.add_XSTR("Exit", 1420, Buttons[EXIT_BUTTON][gr_screen.res].xt,  Buttons[EXIT_BUTTON][gr_screen.res].yt, &Buttons[EXIT_BUTTON][gr_screen.res].button, UI_XSTR_COLOR_PINK);

	if (Player->flags & PLAYER_FLAGS_IS_MULTI) {
		Buttons[SIMULATOR_BUTTON][gr_screen.res].button.disable();
		Buttons[CUTSCENES_BUTTON][gr_screen.res].button.disable();
	}

	Buttons[EXIT_BUTTON][gr_screen.res].button.set_hotkey(KEY_CTRLED | KEY_ENTER);

	Background_bitmap = bm_load(Credits_bitmap_fname[gr_screen.res]);

	Credits_bmps.resize(Credits_num_images);
	for (i=0; i<Credits_num_images; i++) {
		Credits_bmps[i] = -1;
	}
}