/**
 * Creates an executable shader.
 *
 * @param vs	Vertex shader source code
 * @param fs	Fragment shader source code
 * @param gs	Geometry shader source code
 * @return 	Internal ID of compiled and linked shader generated by OpenGL
 */
GLhandleARB opengl_shader_create(const SCP_vector<SCP_string>& vs, const SCP_vector<SCP_string>& fs, const SCP_vector<SCP_string>& gs)
{
	GLhandleARB vs_o = 0;
	GLhandleARB fs_o = 0;
	GLhandleARB gs_o = 0;
	GLhandleARB program = 0;

	if (!vs.empty()) {
		vs_o = opengl_shader_compile_object( vs, GL_VERTEX_SHADER_ARB );

		if ( !vs_o ) {
			mprintf(("ERROR! Unable to create vertex shader!\n"));
			goto Done;
		}
	}

	if (!fs.empty()) {
		fs_o = opengl_shader_compile_object( fs, GL_FRAGMENT_SHADER_ARB );

		if ( !fs_o ) {
			mprintf(("ERROR! Unable to create fragment shader!\n"));
			goto Done;
		}
	}

	if (!gs.empty()) {
		gs_o = opengl_shader_compile_object( gs, GL_GEOMETRY_SHADER_EXT );

		if ( !gs_o ) {
			mprintf(("ERROR! Unable to create fragment shader!\n"));
			goto Done;
		}
	}

	program = opengl_shader_link_object(vs_o, fs_o, gs_o);

	if ( !program ) {
		mprintf(("ERROR! Unable to create shader program!\n"));
	}

Done:
	if (vs_o) {
		vglDeleteObjectARB(vs_o);
	}

	if (fs_o) {
		vglDeleteObjectARB(fs_o);
	}

	if (gs_o) {
		vglDeleteObjectARB(gs_o);
	}

	return program;
}
void obj_find_overlap_colliders(SCP_vector<int> *overlap_list_out, SCP_vector<int> *list, int axis, bool collide)
{
	TRACE_SCOPE(tracing::FindOverlapColliders);

	size_t i, j;
	bool overlapped;
	bool first_not_added = true;
	SCP_vector<int> overlappers;

	float min;
	float overlap_max;
	
	overlappers.clear();

	for ( i = 0; i < (*list).size(); ++i ) {
		overlapped = false;

		min = obj_get_collider_endpoint((*list)[i], axis, true);

		for ( j = 0; j < overlappers.size(); ) {
			overlap_max = obj_get_collider_endpoint(overlappers[j], axis, false);
			if ( min <= overlap_max ) {
				overlapped = true;

				if ( overlappers.size() == 1 && first_not_added ) {
					first_not_added = false;
					overlap_list_out->push_back(overlappers[j]);
				}
				
				if ( collide ) {
					obj_collide_pair(&Objects[(*list)[i]], &Objects[overlappers[j]]);
				}
			} else {
				overlappers[j] = overlappers.back();
				overlappers.pop_back();
				continue;
			}

			++j;
		}

		if ( overlappers.empty() ) {
			first_not_added = true;
		}

		if ( overlapped ) {
			overlap_list_out->push_back((*list)[i]);
		}

		overlappers.push_back((*list)[i]);
	}

	overlapped = true;
}
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;
	}
}
/**
 * Parse the sounds.tbl file, and load the specified sounds.
 */
void gamesnd_parse_soundstbl()
{
	parse_sound_table("sounds.tbl");

	parse_modular_table("*-snd.tbm", parse_sound_table);

	// if we are missing any species then report
	if (!missingFlybySounds.empty())
	{
		SCP_string errorString;
		for (size_t i = 0; i < missingFlybySounds.size(); i++)
		{
			errorString.append(missingFlybySounds[i].species_name);
			errorString.append("\n");
		}

		Error(LOCATION, "The following species are missing flyby sounds in sounds.tbl:\n%s", errorString.c_str());
	}

	missingFlybySounds.clear();
}
static void process_pending_data() {
    while (!pending_frame_data.empty()) {
        auto& frame_data = pending_frame_data.front();

        bool finished;
        if (!do_gpu_queries) {
            finished = true;
        } else {
            // Determine if all queries have passed already
            finished = true;
            for (auto& trace_data : frame_data.data) {
                if (trace_data.gpu_query == -1) {
                    // Event has been processed before
                    continue;
                }

                if (gr_query_value_available(trace_data.gpu_query)) {
                    trace_data.gpu_time = gr_get_query_value(trace_data.gpu_query);
                    free_query_object(trace_data.gpu_query);
                    trace_data.gpu_query = -1;
                } else {
                    // If we are here then a query hasn't finished yet. Try again next time...
                    finished = false;
                    break;
                }
            }
        }

        if (finished) {
            std::thread writer_thread(std::bind(write_json_data, frame_data));
            writer_thread.detach();

            pending_frame_data.erase(pending_frame_data.begin());
        } else {
            // GPU queries always finish in sequence so the later queries can't be finished yet
            break;
        }
    }
}
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();
}
void profile_deinit()
{
    if (Cmdline_profile_write_file)
    {
        if (profiling_file.is_open())
        {
            profiling_file.flush();
            profiling_file.close();
        }
    }
    if (Cmdline_json_profiling)
    {
        profile_dump_json_output();
        while (!pending_frame_data.empty()) {
            process_pending_data();
        }
    }

    for (auto obj : query_objects) {
        gr_delete_query_object(obj);
    }
    query_objects.clear();
    SCP_queue<int>().swap(free_query_objects);
}
static void find_capture_device()
{
	const char *user_device = os_config_read_string( "Sound", "CaptureDevice", NULL );
	const char *default_device = (const char*) alcGetString( NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER );

	// 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;
	}

    if ( alcIsExtensionPresent(NULL, (const ALCchar*)"ALC_ENUMERATION_EXT") == AL_TRUE ) {
		const char *all_devices = (const char*) alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);

		const char *str_list = all_devices;
		int ext_length = 0;

		if ( (str_list != NULL) && ((ext_length = strlen(str_list)) > 0) ) {
			while (ext_length) {
				OALdevice new_device(str_list);

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

				CaptureDevices.push_back( new_device );

				str_list += (ext_length + 1);
				ext_length = strlen(str_list);
			}
		}
	} else {
		if (default_device) {
			OALdevice new_device(default_device);
			new_device.type = OAL_DEVICE_DEFAULT;

			CaptureDevices.push_back( new_device );
		}

		if (user_device) {
			OALdevice new_device(user_device);
			new_device.type = OAL_DEVICE_USER;

			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;
	}
}
void particle_render_all()
{
	ubyte flags;
	float pct_complete;
	float alpha;
	vertex pos;
	vec3d ts, te, temp;
	int rotate = 1;
	int framenum, cur_frame;
	bool render_batch = false;
	int tmap_flags = TMAP_FLAG_TEXTURED | TMAP_HTL_3D_UNLIT | TMAP_FLAG_SOFT_QUAD;

	if ( !Particles_enabled )
		return;

	MONITOR_INC( NumParticlesRend, Num_particles );	

	if ( Particles.empty() )
		return;

	for (SCP_vector<particle*>::iterator p = Particles.begin(); p != Particles.end(); ++p) {
		particle* part = *p;
		// skip back-facing particles (ripped from fullneb code)
		// Wanderer - add support for attached particles
		vec3d p_pos;
		if (part->attached_objnum >= 0) {
			vm_vec_unrotate(&p_pos, &part->pos, &Objects[part->attached_objnum].orient);
			vm_vec_add2(&p_pos, &Objects[part->attached_objnum].pos);
		} else {
			p_pos = part->pos;
		}

		if ( vm_vec_dot_to_point(&Eye_matrix.vec.fvec, &Eye_position, &p_pos) <= 0.0f ) {
			continue;
		}

		// calculate the alpha to draw at
		alpha = get_current_alpha(&p_pos);

		// if it's transparent then just skip it
		if (alpha <= 0.0f) {
			continue;
		}

		// make sure "rotate" is enabled for this particle
		rotate = 1;

		// if this is a tracer style particle, calculate tracer vectors
		if (part->tracer_length > 0.0f) {			
			ts = p_pos;
			temp = part->velocity;
			vm_vec_normalize_quick(&temp);
			vm_vec_scale_add(&te, &ts, &temp, part->tracer_length);

			// don't bother rotating
			rotate = 0;
		}

		// rotate the vertex
		if (rotate) {
			flags = g3_rotate_vertex( &pos, &p_pos );

			if ( flags ) {
				continue;
			}

			if (!Cmdline_nohtl)
				g3_transfer_vertex(&pos, &p_pos);
		}

		// pct complete for the particle
		pct_complete = part->age / part->max_life;

		// figure out which frame we should be using
		if (part->nframes > 1) {
			framenum = fl2i(pct_complete * part->nframes + 0.5);
			CLAMP(framenum, 0, part->nframes-1);

			cur_frame = part->reverse ? (part->nframes - framenum - 1) : framenum;
		} else {
			cur_frame = 0;
		}

		if (part->type == PARTICLE_DEBUG) {
			gr_set_color( 255, 0, 0 );
			g3_draw_sphere_ez( &p_pos, part->radius );
		} else {
			framenum = part->optional_data;

			Assert( cur_frame < part->nframes );

			// if this is a tracer style particle
			if (part->tracer_length > 0.0f) {
				batch_add_laser( framenum + cur_frame, &ts, part->radius, &te, part->radius );
			}
			// draw as a regular bitmap
			else {
				batch_add_bitmap( framenum + cur_frame, tmap_flags, &pos, part->particle_index % 8, part->radius, alpha );
			}

			render_batch = true;
		}
	}

	profile_begin("Batch Render");
	if (render_batch) {
		batch_render_all(Particle_buffer_object);
	}
	profile_end("Batch Render");
}
static void find_playback_device(OpenALInformation* info)
{
	const char *user_device = os_config_read_string( "Sound", "PlaybackDevice", NULL );
	const char *default_device = info->default_playback_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->playback_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;
		}

		PlaybackDevices.push_back( new_device );
	}

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

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


	ALCdevice *device = NULL;
	ALCcontext *context = NULL;

	// for each device that we have available, try and figure out which to use
	for (size_t idx = 0; idx < PlaybackDevices.size(); idx++) {
		OALdevice *pdev = &PlaybackDevices[idx];

		// open our specfic device
		device = alcOpenDevice( (const ALCchar*) pdev->device_name.c_str() );

		if (device == NULL) {
			continue;
		}

		context = alcCreateContext(device, NULL);

		if (context == NULL) {
			alcCloseDevice(device);
			continue;
		}

		alcMakeContextCurrent(context);
		alcGetError(device);

		// check how many sources we can create
		static const int MIN_SOURCES = 48;	// MAX_CHANNELS + 16 spare
		int si = 0;

		for (si = 0; si < MIN_SOURCES; si++) {
			ALuint source_id = 0;
			alGenSources(1, &source_id);

			if (alGetError() != AL_NO_ERROR) {
				break;
			}

			alDeleteSources(1, &source_id);
		}

		if (si == MIN_SOURCES) {
			// ok, it supports our minimum requirements
			pdev->usable = true;

			// need this for the future
			Playback_device = pdev->device_name;

			// done
			break;
		} else {
			// clean up for next pass
			alcMakeContextCurrent(NULL);
			alcDestroyContext(context);
			alcCloseDevice(device);

			context = NULL;
			device = NULL;
		}
	}

	alcMakeContextCurrent(NULL);

	if (context) {
		alcDestroyContext(context);
	}

	if (device) {
		alcCloseDevice(device);
	}
}
void fs2netd_spew_table_checksums(char *outfile)
{
	char full_name[MAX_PATH_LEN];
	int count;
	FILE *out = NULL;
	char description[512] = { 0 };
	char filename[65] = { 0 };
	size_t offset = 0;
	char *p = NULL;

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

	cf_create_default_path_string(full_name, sizeof(full_name) - 1, CF_TYPE_ROOT, outfile);

	// open the outfile
	out = fopen(full_name, "wt");

	if (out == NULL) {
		return;
	}

	p = Cmdline_spew_table_crcs;

	while (*p && (offset < sizeof(description))) {
		if (*p == '"') {
			description[offset++] = '"';
			description[offset++] = '"';
		} else {
			description[offset++] = *p;
		}

		p++;
	}

	// header
	fprintf(out, "filename,CRC32,description\r\n");

	count = (int)Table_valid_status.size();

	// do all the checksums
	for (SCP_vector<crc_valid_status>::iterator tvs = Table_valid_status.begin(); tvs != Table_valid_status.end(); ++tvs) {
		offset = 0;
		p = tvs->name;

		while (*p && (offset < sizeof(filename))) {
			if (*p == '"') {
				filename[offset++] = '"';
				filename[offset++] = '"';
			} else {
				filename[offset++] = *p;
			}

			p++;
		}

		filename[offset] = '\0';

		fprintf(out, "\"%s\",%u,\"%s\"\r\n", filename, tvs->crc32, description);
	}

	fflush(out);
	fclose(out);
}
int fs2netd_update_valid_tables()
{
	int rc;
	int hacked = 0;

	if ( !Logged_in ) {
		return -1;
	}

	// if there are no tables to check with then bail
	if ( Table_valid_status.empty() ) {
		return -1;
	}

	// if we're a standalone, show a dialog saying "validating tables"
	if (Game_mode & GM_STANDALONE_SERVER) {
		std_create_gen_dialog("Validating tables");
		std_gen_set_text("Querying FS2NetD:", 1);
	}

	do_full_packet = true;

	In_process = true;

	if (Is_standalone) {
		do { rc = fs2netd_update_valid_tables_do(); } while (!rc);
	} else {
		rc = popup_till_condition(fs2netd_update_valid_tables_do, XSTR("&Cancel", 779), XSTR("Starting table validation", 1592));
	}

	In_process = false;
	Local_timeout = -1;

	switch (rc) {
		// canceled by popup
		case 0:
			return -1;

		// timed out
		case 1: {
			if ( !Is_standalone ) {
				popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR("Table validation timed out!", 1593));
			}

			return -1;
		}

		// no tables
		case 2: {
			if ( !Is_standalone ) {
				popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR("No tables are available from the server for validation!", 1594));
			}

			return -1;
		}
	}

	// output the status of table validity to multi.log
	for (SCP_vector<crc_valid_status>::iterator tvs = Table_valid_status.begin(); tvs != Table_valid_status.end(); ++tvs) {
		if (tvs->valid) {
			ml_printf("FS2NetD Table Check: '%s' -- Valid!", tvs->name);
		} else {
			ml_printf("FS2NetD Table Check: '%s' -- INVALID (0x%x)!", tvs->name, tvs->crc32);
			hacked = 1;
		}
	}

	// if we're a standalone, kill the validate dialog
	if (Game_mode & GM_STANDALONE_SERVER) {
		std_destroy_gen_dialog();
	}

	return hacked;
}
int fs2netd_get_valid_missions_do()
{
	if (Local_timeout == -1) {
		Local_timeout = timer_get_seconds() + 30;
	}

	// get the available CRCs from the server if we need to
	if ( FS2NetD_file_list.empty() ) {
		int rc = FS2NetD_GetMissionsList(FS2NetD_file_list, do_full_packet);

		do_full_packet = false;

		// communications error
		if (rc < 0) {
			Local_timeout = -1;
			return 4;
		}

		// no missions
		if ( rc && FS2NetD_file_list.empty() ) {
			Local_timeout = -1;
			return 2;
		}

		// if timeout passes then bail on crc failure
		if ( timer_get_seconds() > Local_timeout ) {
			Local_timeout = -1;
			return 1;
		}
	}
	// we should have the CRCs, or there were no missions, so process them
	else {
		static char **file_names = NULL;
		static int idx = 0, count = 0;

		bool found = false;
		int file_index = 0;
		char valid_status = MVALID_STATUS_UNKNOWN;
		char full_name[MAX_FILENAME_LEN], wild_card[10];
		char val_text[MAX_FILENAME_LEN+15];
		uint checksum = 0;

		if (file_names == NULL) {
			// allocate filename space	
			file_names = (char**) vm_malloc_q( sizeof(char*) * 1024 ); // 1024 files should be safe!

			if (file_names == NULL) {
				Local_timeout = -1;
				return 3;
			}

			memset( wild_card, 0, sizeof(wild_card) );
			strcpy_s( wild_card, NOX("*") );
			strcat_s( wild_card, FS_MISSION_FILE_EXT );

			idx = count = cf_get_file_list(1024, file_names, CF_TYPE_MISSIONS, wild_card);
		}

		// drop idx first thing
		idx--;

		// we should be done validating, or just not have nothing to validate
		if (idx < 0) {
			for (idx = 0; idx < count; idx++) {
				if (file_names[idx] != NULL) {
					vm_free(file_names[idx]);
					file_names[idx] = NULL;
				}
			}

			vm_free(file_names);
			file_names = NULL;

			idx = count = 0;

			Local_timeout = -1;
			return 4;
		}


		// verify all filenames that we know about with their CRCs
		// NOTE: that this is done for one file per frame, since this is inside of a popup
		memset( full_name, 0, MAX_FILENAME_LEN );
		strncpy( full_name, cf_add_ext(file_names[idx], FS_MISSION_FILE_EXT), sizeof(full_name) - 1 );

		memset( val_text, 0, sizeof(val_text) );
		snprintf( val_text, sizeof(val_text) - 1, "Validating:  %s", full_name );

		if (Is_standalone) {
			if ( std_gen_is_active() ) {
				std_gen_set_text(val_text, 1);
			}
		} else {
			popup_change_text(val_text);
		}

		cf_chksum_long(full_name, &checksum);

		// try and find the file
		file_index = multi_create_lookup_mission(full_name);

		found = false;

		if (file_index >= 0) {
			for (SCP_vector<file_record>::iterator fr = FS2NetD_file_list.begin(); fr != FS2NetD_file_list.end() && !found; ++fr) {
				if ( !stricmp(full_name, fr->name) ) {
					if (fr->crc32 == checksum) {
						found = true;
						valid_status = MVALID_STATUS_VALID;
					} else {
						valid_status = MVALID_STATUS_INVALID;
					}

					Multi_create_mission_list[file_index].valid_status = valid_status;
				}
			}

			if (found) {
				ml_printf("FS2NetD Mission Validation: %s  =>  Valid!", full_name);
			} else {
				ml_printf("FS2NetD Mission Validation: %s  =>  INVALID! -- 0x%08x", full_name, checksum);
			}
		}
	}

	return 0;
}
void particle_move_all(float frametime)
{
	MONITOR_INC( NumParticles, Num_particles );	

	if ( !Particles_enabled )
		return;

	if ( Particles.empty() )
		return;

	for (SCP_vector<particle*>::iterator p = Particles.begin(); p != Particles.end(); )
	{	
		particle* part = *p;
		if (part->age == 0.0f) {
			part->age = 0.00001f;
		} else {
			part->age += frametime;
		}

		bool remove_particle = false;

		// if its time expired, remove it
		if (part->age > part->max_life) {
			// special case, if max_life is 0 then we want it to render at least once
			if ( (part->age > frametime) || (part->max_life > 0.0f) ) {
				remove_particle = true;
			}
		}

		// if the particle is attached to an object which has become invalid, kill it
		if (part->attached_objnum >= 0) {
			// if the signature has changed, or it's bogus, kill it
			if ( (part->attached_objnum >= MAX_OBJECTS) || (part->attached_sig != Objects[part->attached_objnum].signature) ) {
				remove_particle = true;
			}
		}

		if (remove_particle)
		{
			part->signature = 0;
			delete part;

			// if we're sitting on the very last particle, popping-back will invalidate the iterator!
			if (p + 1 == Particles.end())
			{
				Particles.pop_back();
				break;
			}
			else
			{
				*p = Particles.back();
				Particles.pop_back();
				continue;
			}
		}

		// move as a regular particle
		vm_vec_scale_add2( &part->pos, &part->velocity, frametime );

		// next particle
		++p;
	}
}
/*
 * Take the red alert status information, and adjust the red alert ships accordingly
 * "red alert ships" are wingmen and any ship with the red-alert-carry flag
 * Wingmen without red alert data still need to be handled / removed
 */
void red_alert_bash_wingman_status()
{
	int j;
	ship_obj			*so;

	SCP_vector<red_alert_ship_status>::iterator rasii;
	SCP_vector<p_object>::iterator poii;

	SCP_unordered_map<int, int> Wing_pobjects_deleted;
	SCP_unordered_map<int, int>::iterator ii;

	if ( !(Game_mode & GM_CAMPAIGN_MODE) ) {
		return;
	}

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

	// go through all ships in the game, and see if there is red alert status data for any

	so = GET_FIRST(&Ship_obj_list);
	for ( ; so != END_OF_LIST(&Ship_obj_list); )
	{
		object *ship_objp = &Objects[so->objnum];
		Assert(ship_objp->type == OBJ_SHIP);
		ship *shipp = &Ships[ship_objp->instance];

		if ( !(shipp->flags[Ship::Ship_Flags::From_player_wing]) && !(shipp->flags[Ship::Ship_Flags::Red_alert_store_status]) ) {
			so = GET_NEXT(so);
			continue;
		}

		bool ship_data_restored = false;
		int ship_state = RED_ALERT_DESTROYED_SHIP_CLASS;

		for ( rasii = Red_alert_wingman_status.begin(); rasii != Red_alert_wingman_status.end(); ++rasii )
		{
			red_alert_ship_status *ras = &(*rasii);

			// red-alert data matches this ship!
			if ( !stricmp(ras->name.c_str(), shipp->ship_name)  )
			{
				// we only want to restore ships which haven't been destroyed, or were removed by the player
				if ( (ras->ship_class != RED_ALERT_DESTROYED_SHIP_CLASS) && (ras->ship_class != RED_ALERT_PLAYER_DEL_SHIP_CLASS) )
				{
					// if necessary, restore correct ship class
					if ( ras->ship_class != shipp->ship_info_index )
					{
						if (ras->ship_class >= 0 && ras->ship_class < static_cast<int>(Ship_info.size()))
							change_ship_type(SHIP_INDEX(shipp), ras->ship_class);
						else
							mprintf(("Invalid ship class specified in red alert data for ship %s. Using mission defaults.\n", shipp->ship_name));
					}

					// restore hull (but not shields)
					if (ras->hull >= 0.0f && ras->hull <= ship_objp->hull_strength)
						ship_objp->hull_strength = ras->hull;
					else
						mprintf(("Invalid health in red alert data for ship %s. Using mission defaults.\n", shipp->ship_name));

					// restore weapons and subsys
					red_alert_bash_weapons(ras, &shipp->weapons);
					red_alert_bash_subsys_status(ras, shipp);

					ship_data_restored = true;
				}
				// must be destroyed or deleted
				else
				{
					ship_state = ras->ship_class;
				}

				// we won't have two ships with the same name, so bail
				break;
			}
		}

		// remove ship if it was destroyed, or if there's no red-alert data for it
		if ( !ship_data_restored ) {
			// we need to be a little tricky here because deletion invalidates the ship_obj
			ship_obj *next_so = GET_NEXT(so);
			red_alert_delete_ship(ship_objp->instance, ship_state);
			so = next_so;
		} else {
			so = GET_NEXT(so);
		}
	}

	// NOTE: in retail, red alert data was not loaded for ships that arrived later in the mission
	if (!Red_alert_applies_to_delayed_ships)
		return;

	// go through all ships yet to arrive, and see if there is red alert status data for any

	for ( poii = Parse_objects.begin(); poii != Parse_objects.end(); ++poii )
	{
		p_object *pobjp = &(*poii);

		// objects that have already arrived would have been handled in the above loop
		if ( pobjp->created_object != NULL )
			continue;

		// if we're in a wing, check whether we're in the player wing
		bool from_player_wing = false;
		if (pobjp->wingnum >= 0)
		{
			for (j = 0; j < MAX_STARTING_WINGS; j++)
			{
				if (!stricmp(Starting_wing_names[j], Wings[pobjp->wingnum].name))
				{
					from_player_wing = true;
					break;
				}
			}
		}

		// same condition as in ship_obj loop
		if ( !from_player_wing && !(pobjp->flags[Mission::Parse_Object_Flags::SF_Red_alert_store_status]) ) {
			continue;
		}

		bool ship_data_restored = false;
		int ship_state = RED_ALERT_DESTROYED_SHIP_CLASS;

		for ( rasii = Red_alert_wingman_status.begin(); rasii != Red_alert_wingman_status.end(); ++rasii )
		{
			red_alert_ship_status *ras = &(*rasii);

			// red-alert data matches this ship!
			if ( !stricmp(ras->name.c_str(), pobjp->name)  )
			{
				// we only want to restore ships which haven't been destroyed, or were removed by the player
				if ( (ras->ship_class != RED_ALERT_DESTROYED_SHIP_CLASS) && (ras->ship_class != RED_ALERT_PLAYER_DEL_SHIP_CLASS) )
				{
					// if necessary, restore correct ship class
					if ( ras->ship_class != pobjp->ship_class )
					{
						if (ras->ship_class >= 0 && ras->ship_class < static_cast<int>(Ship_info.size()))
							swap_parse_object(pobjp, ras->ship_class);
						else
						{
							mprintf(("Invalid ship class specified in red alert data for ship %s. Using mission defaults.\n", pobjp->name));
							
							// We will break anyway to this should work
							break;
						}
					}

					// restore hull (but not shields)
					if (ras->hull >= 0.0f && ras->hull <= (pobjp->initial_hull * pobjp->ship_max_hull_strength / 100.0f))
						pobjp->initial_hull = (int) (ras->hull * 100.0f / pobjp->ship_max_hull_strength);
					else
						mprintf(("Invalid health in red alert data for ship %s. Using mission defaults.\n", pobjp->name));

					// restore weapons and subsys
					red_alert_bash_weapons(ras, pobjp);
					red_alert_bash_subsys_status(ras, pobjp);

					ship_data_restored = true;
				}
				// must be destroyed or deleted
				else
				{
					ship_state = ras->ship_class;
				}

				// we won't have two ships with the same name, so bail
				break;
			}
		}

		// remove ship if it was destroyed, or if there's no red-alert data for it
		if ( !ship_data_restored )
		{
			red_alert_delete_ship(pobjp, ship_state);

			if (pobjp->wingnum >= 0)
				Wing_pobjects_deleted[pobjp->wingnum]++;
		}
	}

	// if all parse objects in a wing have been removed, decrement the count for that wing
	for (ii = Wing_pobjects_deleted.begin(); ii != Wing_pobjects_deleted.end(); ++ii)
	{
		wing *wingp = &Wings[ii->first];

		if (wingp->num_waves > 0 && wingp->wave_count == ii->second)
		{			
			wingp->current_wave++;
			wingp->red_alert_skipped_ships += wingp->wave_count;

            if (wingp->num_waves == 0)
                wingp->flags.set(Ship::Wing_Flags::Gone);

			// look through all ships yet to arrive...
			for (p_object *pobjp = GET_FIRST(&Ship_arrival_list); pobjp != END_OF_LIST(&Ship_arrival_list); pobjp = GET_NEXT(pobjp))
			{
				// ...and mark the ones in this wing
				if (pobjp->wingnum == ii->first)
				{
					// no waves left to arrive, so mark ships accordingly
                    if (wingp->num_waves == 0)
                        pobjp->flags.set(Mission::Parse_Object_Flags::SF_Cannot_arrive);
                    // we skipped one complete wave, so clear the flag so the next wave creates all ships
                    else
                        pobjp->flags.remove(Mission::Parse_Object_Flags::Red_alert_deleted);
				}
			}
		}
	}
}
static void find_playback_device()
{
	const char *user_device = os_config_read_string( "Sound", "PlaybackDevice", NULL );
	const char *default_device = (const char*) alcGetString( NULL, ALC_DEFAULT_DEVICE_SPECIFIER );

	// 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;
	}

    if ( alcIsExtensionPresent(NULL, (const ALCchar*)"ALC_ENUMERATION_EXT") == AL_TRUE ) {
		const char *all_devices = NULL;

        if ( alcIsExtensionPresent(NULL, (const ALCchar*)"ALC_ENUMERATE_ALL_EXT") == AL_TRUE ) {
			all_devices = (const char*) alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
		} else {
			all_devices = (const char*) alcGetString(NULL, ALC_DEVICE_SPECIFIER);
		}

		const char *str_list = all_devices;
		int ext_length = 0;

		if ( (str_list != NULL) && ((ext_length = strlen(str_list)) > 0) ) {
			while (ext_length) {
				OALdevice new_device(str_list);

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

				PlaybackDevices.push_back( new_device );

				str_list += (ext_length + 1);
				ext_length = strlen(str_list);
			}
		}
	} else {
		if (default_device) {
			OALdevice new_device(default_device);
			new_device.type = OAL_DEVICE_DEFAULT;

			PlaybackDevices.push_back( new_device );
		}

		if (user_device) {
			OALdevice new_device(user_device);
			new_device.type = OAL_DEVICE_USER;

			PlaybackDevices.push_back( new_device );
		}
	}

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

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


	ALCdevice *device = NULL;
	ALCcontext *context = NULL;

	// for each device that we have available, try and figure out which to use
	for (size_t idx = 0; idx < PlaybackDevices.size(); idx++) {
		OALdevice *pdev = &PlaybackDevices[idx];

		// open our specfic device
		device = alcOpenDevice( (const ALCchar*) pdev->device_name.c_str() );

		if (device == NULL) {
			continue;
		}

		context = alcCreateContext(device, NULL);

		if (context == NULL) {
			alcCloseDevice(device);
			continue;
		}

		alcMakeContextCurrent(context);
		alcGetError(device);

		// check how many sources we can create
		static const int MIN_SOURCES = 48;	// MAX_CHANNELS + 16 spare
		int si = 0;

		for (si = 0; si < MIN_SOURCES; si++) {
			ALuint source_id = 0;
			alGenSources(1, &source_id);

			if (alGetError() != AL_NO_ERROR) {
				break;
			}

			alDeleteSources(1, &source_id);
		}

		if (si == MIN_SOURCES) {
			// ok, it supports our minimum requirements
			pdev->usable = true;

			// need this for the future
			Playback_device = pdev->device_name;

			// done
			break;
		} else {
			// clean up for next pass
			alcMakeContextCurrent(NULL);
			alcDestroyContext(context);
			alcCloseDevice(device);

			context = NULL;
			device = NULL;
		}
	}

	alcMakeContextCurrent(NULL);

	if (context) {
		alcDestroyContext(context);
	}

	if (device) {
		alcCloseDevice(device);
	}
}
// initializes hardware device from perferred/default/enumerated list
bool openal_init_device(SCP_string *playback, SCP_string *capture)
{
	if ( !Playback_device.empty() ) {
		if (playback) {
			*playback = Playback_device;
		}

		if (capture) {
			*capture = Capture_device;
		}

		return true;
	}

	if (playback) {
		playback->erase();
	}

	if (capture) {
		capture->erase();
	}

	// This reuses the code for the launcher to make sure everything is consistent
	auto platform_info = openal_get_platform_information();

	if (platform_info.version_major <= 1 && platform_info.version_minor < 1) {
		os::dialogs::Message(os::dialogs::MESSAGEBOX_ERROR,
			"OpenAL 1.1 or newer is required for proper operation. On Linux and Windows OpenAL Soft is recommended. If you are on Mac OS X you need to upgrade your OS.");
		return false;
	}

	// go through and find out what devices we actually want to use ...
	find_playback_device(&platform_info);
	find_capture_device(&platform_info);

	if ( Playback_device.empty() ) {
		return false;
	}


#ifndef NDEBUG
	if ( !PlaybackDevices.empty() ) {
		nprintf(("OpenAL", "  Available Playback Devices:\n"));

		for (size_t idx = 0; idx < PlaybackDevices.size(); idx++) {
			nprintf(("OpenAL", "    %s", PlaybackDevices[idx].device_name.c_str()));

			if (PlaybackDevices[idx].type == OAL_DEVICE_USER) {
				nprintf(("OpenAL", "  *preferred*\n"));
			} else if (PlaybackDevices[idx].type == OAL_DEVICE_DEFAULT) {
				nprintf(("OpenAL", "  *default*\n"));
			} else {
				nprintf(("OpenAL", "\n"));
			}
		}
	}

	if ( !CaptureDevices.empty() ) {
		if ( !PlaybackDevices.empty() ) {
			nprintf(("OpenAL", "\n"));
		}

		nprintf(("OpenAL", "  Available Capture Devices:\n"));

		for (size_t idx = 0; idx < CaptureDevices.size(); idx++) {
			nprintf(("OpenAL", "    %s", CaptureDevices[idx].device_name.c_str()));

			if (CaptureDevices[idx].type == OAL_DEVICE_USER) {
				nprintf(("OpenAL", "  *preferred*\n"));
			} else if (CaptureDevices[idx].type == OAL_DEVICE_DEFAULT) {
				nprintf(("OpenAL", "  *default*\n"));
			} else {
				nprintf(("OpenAL", "\n"));
			}
		}

		nprintf(("OpenAL", "\n"));
	}
#endif


	// cleanup
	PlaybackDevices.clear();
	CaptureDevices.clear();


	if (playback) {
		*playback = Playback_device;
	}

	if (capture) {
		*capture = Capture_device;
	}

	return true;
}
/*
 * Record the current state of the players wingman & ships with the "red-alert-carry" flag
 * Wingmen without the red-alert-carry flag are only stored if they survive
 * dead wingmen must still be handled in red_alert_bash_wingman_status
 */
void red_alert_store_wingman_status()
{
	ship				*shipp;
	red_alert_ship_status	ras;
	ship_obj			*so;
	object			*ship_objp;

	// store the mission filename for the red alert precursor mission
	Red_alert_precursor_mission = Game_current_mission_filename;

	// Pyro3d - Clear list of stored red alert ships 
	// Probably not the best solution, but it prevents an assertion in change_ship_type()
	Red_alert_wingman_status.clear();

	// store status for all existing ships
	for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
		ship_objp = &Objects[so->objnum];
		Assert(ship_objp->type == OBJ_SHIP);
		shipp = &Ships[ship_objp->instance];

		if ( shipp->flags[Ship::Ship_Flags::Dying] ) {
			continue;
		}

		if ( !(shipp->flags[Ship::Ship_Flags::From_player_wing]) && !(shipp->flags[Ship::Ship_Flags::Red_alert_store_status]) ) {
			continue;
		}

		ras.name = shipp->ship_name;
		ras.hull = Objects[shipp->objnum].hull_strength;
		ras.ship_class = shipp->ship_info_index;
		red_alert_store_weapons(&ras, &shipp->weapons);
		red_alert_store_subsys_status(&ras, shipp);

		Red_alert_wingman_status.push_back( ras );
		// niffiwan: trying to track down red alert bug creating HUGE pilot files 
		Assert( (Red_alert_wingman_status.size() <= MAX_SHIPS) );
	}

	// store exited ships that did not die
	for (int idx=0; idx<(int)Ships_exited.size(); idx++) {
		if (Ships_exited[idx].flags[Ship::Exit_Flags::Red_alert_carry]) {
			ras.name = Ships_exited[idx].ship_name;
			ras.hull = float(Ships_exited[idx].hull_strength);

			// if a ship has been destroyed or removed manually by the player, then mark it as such ...
			if ( Ships_exited[idx].flags[Ship::Exit_Flags::Destroyed]) {
				ras.ship_class = RED_ALERT_DESTROYED_SHIP_CLASS;
			}
			else if (Ships_exited[idx].flags[Ship::Exit_Flags::Player_deleted]) {
				ras.ship_class = RED_ALERT_PLAYER_DEL_SHIP_CLASS;
			}
			// ... otherwise we want to make sure and carry over the ship class
			else {
				Assert( Ships_exited[idx].ship_class >= 0 );
				ras.ship_class = Ships_exited[idx].ship_class;
			}

			red_alert_store_weapons(&ras, NULL);
			red_alert_store_subsys_status(&ras, NULL);

			Red_alert_wingman_status.push_back( ras );
			// niffiwan: trying to track down red alert bug creating HUGE pilot files 
			Assert( (Red_alert_wingman_status.size() <= MAX_SHIPS) );
		}
	}

	Assert( !Red_alert_wingman_status.empty() );
}
// initializes hardware device from perferred/default/enumerated list
bool openal_init_device(SCP_string *playback, SCP_string *capture)
{
	if ( !Playback_device.empty() ) {
		if (playback) {
			*playback = Playback_device;
		}

		if (capture) {
			*capture = Capture_device;
		}

		return true;
	}

	if (playback) {
		playback->erase();
	}

	if (capture) {
		capture->erase();
	}

	// initialize default setup first, for version check...

	ALCdevice *device = alcOpenDevice(NULL);

	if (device == NULL) {
		return false;
	}

	ALCcontext *context = alcCreateContext(device, NULL);

	if (context == NULL) {
		alcCloseDevice(device);
		return false;
	}

	alcMakeContextCurrent(context);

	// version check (for 1.0 or 1.1)
	ALCint AL_minor_version = 0;
	alcGetIntegerv(NULL, ALC_MINOR_VERSION, sizeof(ALCint), &AL_minor_version);

	if (AL_minor_version < 1) {
#ifdef _WIN32
		MessageBox(NULL, "OpenAL 1.1 or newer is required for proper operation.  Please upgrade your OpenAL drivers, which\nare available at http://www.openal.org/downloads.html, and try running the game again.", NULL, MB_OK);
#else
		printf("OpenAL 1.1 or newer is required for proper operation.\n");
		printf("Please upgrade to a newer version if on OS X or switch\n");
		printf("to OpenAL-Soft on Linux.\n");
#endif

		alcMakeContextCurrent(NULL);
		alcDestroyContext(context);
		alcCloseDevice(device);

		return false;
	}

	alcGetError(device);

	// close default device
	alcMakeContextCurrent(NULL);
	alcDestroyContext(context);
	alcCloseDevice(device);


	// go through and find out what devices we actually want to use ...
	find_playback_device();
	find_capture_device();

	if ( Playback_device.empty() ) {
		return false;
	}


#ifndef NDEBUG
	if ( !PlaybackDevices.empty() ) {
		nprintf(("OpenAL", "  Available Playback Devices:\n"));

		for (size_t idx = 0; idx < PlaybackDevices.size(); idx++) {
			nprintf(("OpenAL", "    %s", PlaybackDevices[idx].device_name.c_str()));

			if (PlaybackDevices[idx].type == OAL_DEVICE_USER) {
				nprintf(("OpenAL", "  *preferred*\n"));
			} else if (PlaybackDevices[idx].type == OAL_DEVICE_DEFAULT) {
				nprintf(("OpenAL", "  *default*\n"));
			} else {
				nprintf(("OpenAL", "\n"));
			}
		}
	}

	if ( !CaptureDevices.empty() ) {
		if ( !PlaybackDevices.empty() ) {
			nprintf(("OpenAL", "\n"));
		}

		nprintf(("OpenAL", "  Available Capture Devices:\n"));

		for (size_t idx = 0; idx < CaptureDevices.size(); idx++) {
			nprintf(("OpenAL", "    %s", CaptureDevices[idx].device_name.c_str()));

			if (CaptureDevices[idx].type == OAL_DEVICE_USER) {
				nprintf(("OpenAL", "  *preferred*\n"));
			} else if (CaptureDevices[idx].type == OAL_DEVICE_DEFAULT) {
				nprintf(("OpenAL", "  *default*\n"));
			} else {
				nprintf(("OpenAL", "\n"));
			}
		}

		nprintf(("OpenAL", "\n"));
	}
#endif


	// cleanup
	PlaybackDevices.clear();
	CaptureDevices.clear();


	if (playback) {
		*playback = Playback_device;
	}

	if (capture) {
		*capture = Capture_device;
	}

	return true;
}
/**
 * @brief Parses controlconfigdefault.tbl, and overrides the default control configuration for each valid entry in the .tbl
 */
void control_config_common_load_overrides()
{
	LoadEnumsIntoMaps();

	try {
		if (cf_exists_full("controlconfigdefaults.tbl", CF_TYPE_TABLES)) {
			read_file_text("controlconfigdefaults.tbl", CF_TYPE_TABLES);
		} else {
			read_file_text_from_default(defaults_get_file("controlconfigdefaults.tbl"));
		}

		reset_parse();

		// start parsing
		// TODO: Split this out into more helps. Too many tabs!
		while(optional_string("#ControlConfigOverride")) {
			config_item *cfg_preset = new config_item[CCFG_MAX + 1];
			std::copy(Control_config, Control_config + CCFG_MAX + 1, cfg_preset);
			Control_config_presets.push_back(cfg_preset);

			SCP_string preset_name;
			if (optional_string("$Name:")) {
				stuff_string_line(preset_name);
			} else {
				preset_name = "<unnamed preset>";
			}
			Control_config_preset_names.push_back(preset_name);

			while (required_string_either("#End","$Bind Name:")) {
				const int iBufferLength = 64;
				char szTempBuffer[iBufferLength];

				required_string("$Bind Name:");
				stuff_string(szTempBuffer, F_NAME, iBufferLength);

				const size_t cCntrlAryLength = sizeof(Control_config) / sizeof(Control_config[0]);
				for (size_t i = 0; i < cCntrlAryLength; ++i) {
					config_item& r_ccConfig = cfg_preset[i];

					if (!strcmp(szTempBuffer, r_ccConfig.text)) {
						/**
                        * short key_default;
                        * short joy_default;
                        * char tab;
                        * bool hasXSTR;
                        * char type;
                        */

						int iTemp;

						if (optional_string("$Key Default:")) {
							if (optional_string("NONE")) {
								r_ccConfig.key_default = (short)-1;
							} else {
								stuff_string(szTempBuffer, F_NAME, iBufferLength);
								r_ccConfig.key_default = (short)mKeyNameToVal[szTempBuffer];
							}
						}

						if (optional_string("$Joy Default:")) {
							stuff_int(&iTemp);
							r_ccConfig.joy_default = (short)iTemp;
						}

						if (optional_string("$Key Mod Shift:")) {
							stuff_int(&iTemp);
							r_ccConfig.key_default |= (iTemp == 1) ? KEY_SHIFTED : 0;
						}

						if (optional_string("$Key Mod Alt:")) {
							stuff_int(&iTemp);
							r_ccConfig.key_default |= (iTemp == 1) ? KEY_ALTED : 0;
						}

						if (optional_string("$Key Mod Ctrl:")) {
							stuff_int(&iTemp);
							r_ccConfig.key_default |= (iTemp == 1) ? KEY_CTRLED : 0;
						}

						if (optional_string("$Category:")) {
							stuff_string(szTempBuffer, F_NAME, iBufferLength);
							r_ccConfig.tab = (char)mCCTabNameToVal[szTempBuffer];
						}

						if (optional_string("$Has XStr:")) {
							stuff_int(&iTemp);
							r_ccConfig.hasXSTR = (iTemp == 1);
						}

						if (optional_string("$Type:")) {
							stuff_string(szTempBuffer, F_NAME, iBufferLength);
							r_ccConfig.type = (char)mCCTypeNameToVal[szTempBuffer];
						}

						if (optional_string("+Disable")) {
							r_ccConfig.disabled = true;
						}
						if (optional_string("$Disable:")) {
							stuff_boolean(&r_ccConfig.disabled);
						}

						// Nerf the buffer now.
						szTempBuffer[0] = '\0';
					} else if ((i + 1) == cCntrlAryLength) {
						error_display(1, "Bind Name not found: %s\n", szTempBuffer);
						advance_to_eoln(NULL);
						ignore_white_space();
						return;
					}
				}
			}

			required_string("#End");
		}
	}
	catch (const parse::ParseException& e)
	{
		mprintf(("TABLES: Unable to parse 'controlconfigdefaults.tbl'!  Error message = %s.\n", e.what()));
		return;
	}

	// Overwrite the control config with the first preset that was found
	if (!Control_config_presets.empty()) {
		std::copy(Control_config_presets[0], Control_config_presets[0] + CCFG_MAX + 1, Control_config);
	}
}
Exemple #21
0
// initializes hardware device from perferred/default/enumerated list
bool openal_init_device(SCP_string *playback, SCP_string *capture)
{
	if ( !Playback_device.empty() ) {
		if (playback) {
			*playback = Playback_device;
		}

		if (capture) {
			*capture = Capture_device;
		}

		return true;
	}

	if (playback) {
		playback->erase();
	}

	if (capture) {
		capture->erase();
	}

	// initialize default setup first, for version check...

	ALCdevice *device = alcOpenDevice(NULL);

	if (device == NULL) {
		return false;
	}

	ALCcontext *context = alcCreateContext(device, NULL);

	if (context == NULL) {
		alcCloseDevice(device);
		return false;
	}

	alcMakeContextCurrent(context);

	// version check (for 1.0 or 1.1)
	ALCint AL_minor_version = 0;
	alcGetIntegerv(NULL, ALC_MINOR_VERSION, sizeof(ALCint), &AL_minor_version);

	if (AL_minor_version < 1) {
		os::dialogs::Message(os::dialogs::MESSAGEBOX_ERROR,
			"OpenAL 1.1 or newer is required for proper operation. On Linux and Windows OpenAL Soft is recommended. If you are on Mac OS X you need to upgrade your OS.");

		alcMakeContextCurrent(NULL);
		alcDestroyContext(context);
		alcCloseDevice(device);

		return false;
	}

	alcGetError(device);

	// close default device
	alcMakeContextCurrent(NULL);
	alcDestroyContext(context);
	alcCloseDevice(device);


	// go through and find out what devices we actually want to use ...
	find_playback_device();
	find_capture_device();

	if ( Playback_device.empty() ) {
		return false;
	}


#ifndef NDEBUG
	if ( !PlaybackDevices.empty() ) {
		nprintf(("OpenAL", "  Available Playback Devices:\n"));

		for (size_t idx = 0; idx < PlaybackDevices.size(); idx++) {
			nprintf(("OpenAL", "    %s", PlaybackDevices[idx].device_name.c_str()));

			if (PlaybackDevices[idx].type == OAL_DEVICE_USER) {
				nprintf(("OpenAL", "  *preferred*\n"));
			} else if (PlaybackDevices[idx].type == OAL_DEVICE_DEFAULT) {
				nprintf(("OpenAL", "  *default*\n"));
			} else {
				nprintf(("OpenAL", "\n"));
			}
		}
	}

	if ( !CaptureDevices.empty() ) {
		if ( !PlaybackDevices.empty() ) {
			nprintf(("OpenAL", "\n"));
		}

		nprintf(("OpenAL", "  Available Capture Devices:\n"));

		for (size_t idx = 0; idx < CaptureDevices.size(); idx++) {
			nprintf(("OpenAL", "    %s", CaptureDevices[idx].device_name.c_str()));

			if (CaptureDevices[idx].type == OAL_DEVICE_USER) {
				nprintf(("OpenAL", "  *preferred*\n"));
			} else if (CaptureDevices[idx].type == OAL_DEVICE_DEFAULT) {
				nprintf(("OpenAL", "  *default*\n"));
			} else {
				nprintf(("OpenAL", "\n"));
			}
		}

		nprintf(("OpenAL", "\n"));
	}
#endif


	// cleanup
	PlaybackDevices.clear();
	CaptureDevices.clear();


	if (playback) {
		*playback = Playback_device;
	}

	if (capture) {
		*capture = Capture_device;
	}

	return true;
}