void opengl_tnl_set_material(material* material_info, bool set_base_map, bool set_clipping)
{
	int shader_handle = material_info->get_shader_handle();
	int base_map = material_info->get_texture_map(TM_BASE_TYPE);
	vec4 clr = material_info->get_color();

	Assert(shader_handle >= 0);

	opengl_shader_set_current(shader_handle);

	if (material_info->has_buffer_blend_modes()) {
		Assertion(GLAD_GL_ARB_draw_buffers_blend != 0,
				  "Buffer blend modes are not supported at the moment! Query the capability before using this feature.");

		auto enable_blend = false;
		for (auto i = 0; i < (int) material::NUM_BUFFER_BLENDS; ++i) {
			auto mode = material_info->get_blend_mode(i);

			GL_state.SetAlphaBlendModei(i, mode);
			enable_blend = enable_blend || mode != ALPHA_BLEND_NONE;
		}
		GL_state.Blend(enable_blend ? GL_TRUE : GL_FALSE);
	} else {
		GL_state.SetAlphaBlendMode(material_info->get_blend_mode());
	}
	GL_state.SetZbufferType(material_info->get_depth_mode());

	gr_set_cull(material_info->get_cull_mode() ? 1 : 0);

	gr_zbias(material_info->get_depth_bias());

	gr_set_fill_mode(material_info->get_fill_mode());

	gr_set_texture_addressing(material_info->get_texture_addressing());

	if (set_clipping) {
		// Only set the clipping state if explicitly requested by the caller to avoid unnecessary state changes
		auto& clip_params = material_info->get_clip_plane();
		if (!clip_params.enabled) {
			GL_state.ClipDistance(0, false);
		} else {
			Assertion(Current_shader != NULL && (Current_shader->shader == SDR_TYPE_MODEL
				|| Current_shader->shader == SDR_TYPE_PASSTHROUGH_RENDER
				|| Current_shader->shader == SDR_TYPE_DEFAULT_MATERIAL),
					  "Clip planes are not supported by this shader!");

			GL_state.ClipDistance(0, true);
		}
	}

	GL_state.StencilMask(material_info->get_stencil_mask());

	auto& stencilFunc = material_info->get_stencil_func();
	GL_state.StencilFunc(convertComparisionFunction(stencilFunc.compare), stencilFunc.ref, stencilFunc.mask);

	auto& frontStencilOp = material_info->get_front_stencil_op();
	GL_state.StencilOpSeparate(GL_FRONT,
							   convertStencilOp(frontStencilOp.stencilFailOperation),
							   convertStencilOp(frontStencilOp.depthFailOperation),
							   convertStencilOp(frontStencilOp.successOperation));
	auto& backStencilOp = material_info->get_back_stencil_op();
	GL_state.StencilOpSeparate(GL_BACK,
							   convertStencilOp(backStencilOp.stencilFailOperation),
							   convertStencilOp(backStencilOp.depthFailOperation),
							   convertStencilOp(backStencilOp.successOperation));

	GL_state.StencilTest(material_info->is_stencil_enabled() ? GL_TRUE : GL_FALSE);

	auto& color_mask = material_info->get_color_mask();
	GL_state.ColorMask(color_mask.x, color_mask.y, color_mask.z, color_mask.w);

	// This is only needed for the passthrough shader
	uint32_t array_index = 0;
	if ( set_base_map && base_map >= 0 ) {
		float u_scale, v_scale;

		if ( !gr_opengl_tcache_set(base_map, material_info->get_texture_type(), &u_scale, &v_scale, &array_index) ) {
			mprintf(("WARNING: Error setting bitmap texture (%i)!\n", base_map));
		}
	}

	if ( Current_shader->shader == SDR_TYPE_DEFAULT_MATERIAL ) {
		opengl_shader_set_default_material(base_map >= 0,
										   material_info->get_texture_type() == TCACHE_TYPE_AABITMAP,
										   &clr,
										   material_info->get_color_scale(),
										   array_index,
										   material_info->get_clip_plane());
	}
}
void opengl_tnl_set_model_material(model_material *material_info)
{
	float u_scale, v_scale;
	int render_pass = 0;

	opengl_tnl_set_material(material_info, false, false);

	if ( GL_state.CullFace() ) {
		GL_state.FrontFaceValue(GL_CW);
	}

	gr_set_center_alpha(material_info->get_center_alpha());

	Assert( Current_shader->shader == SDR_TYPE_MODEL );

	GL_state.Texture.SetShaderMode(GL_TRUE);

	if (GL_workaround_clipping_planes) {
		if (Current_shader->flags & SDR_FLAG_MODEL_CLIP) {
			GL_state.ClipDistance(0, true);
		} else {
			GL_state.ClipDistance(0, false);
		}
	} else {
		if (Current_shader->flags & SDR_FLAG_MODEL_CLIP || Current_shader->flags & SDR_FLAG_MODEL_TRANSFORM) {
			GL_state.ClipDistance(0, true);
		} else {
			GL_state.ClipDistance(0, false);
		}
	}

	uint32_t array_index;
	if ( Current_shader->flags & SDR_FLAG_MODEL_DIFFUSE_MAP ) {
		Current_shader->program->Uniforms.setUniformi("sBasemap", render_pass);

		gr_opengl_tcache_set(material_info->get_texture_map(TM_BASE_TYPE), TCACHE_TYPE_NORMAL, &u_scale, &v_scale, &array_index, render_pass);
		++render_pass;
	}

	if ( Current_shader->flags & SDR_FLAG_MODEL_GLOW_MAP ) {
		Current_shader->program->Uniforms.setUniformi("sGlowmap", render_pass);

		gr_opengl_tcache_set(material_info->get_texture_map(TM_GLOW_TYPE), TCACHE_TYPE_NORMAL, &u_scale, &v_scale, &array_index, render_pass);

		++render_pass;
	}

	if ( Current_shader->flags & SDR_FLAG_MODEL_SPEC_MAP ) {
		Current_shader->program->Uniforms.setUniformi("sSpecmap", render_pass);

		if ( material_info->get_texture_map(TM_SPEC_GLOSS_TYPE) > 0 ) {
			gr_opengl_tcache_set(material_info->get_texture_map(TM_SPEC_GLOSS_TYPE), TCACHE_TYPE_NORMAL, &u_scale, &v_scale, &array_index, render_pass);
		} else {
			gr_opengl_tcache_set(material_info->get_texture_map(TM_SPECULAR_TYPE), TCACHE_TYPE_NORMAL, &u_scale, &v_scale, &array_index, render_pass);
		}
		++render_pass;

		if ( Current_shader->flags & SDR_FLAG_MODEL_ENV_MAP ) {
			Current_shader->program->Uniforms.setUniformi("sEnvmap", render_pass);

			gr_opengl_tcache_set(ENVMAP, TCACHE_TYPE_CUBEMAP, &u_scale, &v_scale, &array_index, render_pass);
			Assertion(array_index == 0, "Cube map arrays are not supported yet!");

			++render_pass;
		}
	}

	if ( Current_shader->flags & SDR_FLAG_MODEL_NORMAL_MAP ) {
		Current_shader->program->Uniforms.setUniformi("sNormalmap", render_pass);

		gr_opengl_tcache_set(material_info->get_texture_map(TM_NORMAL_TYPE), TCACHE_TYPE_NORMAL, &u_scale, &v_scale, &array_index, render_pass);

		++render_pass;
	}

	if ( Current_shader->flags & SDR_FLAG_MODEL_HEIGHT_MAP ) {
		Current_shader->program->Uniforms.setUniformi("sHeightmap", render_pass);

		gr_opengl_tcache_set(material_info->get_texture_map(TM_HEIGHT_TYPE), TCACHE_TYPE_NORMAL, &u_scale, &v_scale, &array_index, render_pass);

		++render_pass;
	}

	if ( Current_shader->flags & SDR_FLAG_MODEL_AMBIENT_MAP ) {
		Current_shader->program->Uniforms.setUniformi("sAmbientmap", render_pass);

		gr_opengl_tcache_set(material_info->get_texture_map(TM_AMBIENT_TYPE), TCACHE_TYPE_NORMAL, &u_scale, &v_scale, &array_index, render_pass);

		++render_pass;
	}

	if ( Current_shader->flags & SDR_FLAG_MODEL_MISC_MAP ) {
		Current_shader->program->Uniforms.setUniformi("sMiscmap", render_pass);

		gr_opengl_tcache_set(material_info->get_texture_map(TM_MISC_TYPE), TCACHE_TYPE_NORMAL, &u_scale, &v_scale, &array_index, render_pass);

		++render_pass;
	}

	if ( Current_shader->flags & SDR_FLAG_MODEL_SHADOWS ) {
		Current_shader->program->Uniforms.setUniformi("shadow_map", render_pass);

		GL_state.Texture.Enable(render_pass, GL_TEXTURE_2D_ARRAY, Shadow_map_texture);

		++render_pass; // bump!
	}

	if ( Current_shader->flags & SDR_FLAG_MODEL_ANIMATED ) {
		Current_shader->program->Uniforms.setUniformi("sFramebuffer", render_pass);

		if ( Scene_framebuffer_in_frame ) {
			GL_state.Texture.Enable(render_pass, GL_TEXTURE_2D, Scene_effect_texture);
			glDrawBuffer(GL_COLOR_ATTACHMENT0);
		} else {
			GL_state.Texture.Enable(render_pass, GL_TEXTURE_2D, Framebuffer_fallback_texture_id);
		}

		++render_pass;
	}

	if ( Current_shader->flags & SDR_FLAG_MODEL_TRANSFORM ) {
		Current_shader->program->Uniforms.setUniformi("transform_tex", render_pass);
		GL_state.Texture.Enable(render_pass, GL_TEXTURE_BUFFER, opengl_get_transform_buffer_texture());

		++render_pass;
	}

	if ( Deferred_lighting ) {
		// don't blend if we're drawing to the g-buffers
		GL_state.SetAlphaBlendMode(ALPHA_BLEND_NONE);
	}
}
Example #3
0
bool pilotfile::save_savefile()
{
	char base[_MAX_FNAME] = { '\0' };
	std::ostringstream buf;

	if (Game_mode & GM_MULTIPLAYER) {
		return false;
	}

	// set player ptr first thing
	Assert( (Player_num >= 0) && (Player_num < MAX_PLAYERS) );
	p = &Players[Player_num];

	if ( !strlen(Campaign.filename) ) {
		return false;
	}

	// build up filename for the savefile...
	_splitpath(Campaign.filename, NULL, NULL, base, NULL);

	buf << p->callsign << "." << base << ".csg";

	filename = buf.str().c_str();

	// make sure that we can actually save this safely
	if (m_data_invalid) {
		mprintf(("CSG => Skipping save of '%s' due to invalid data check!\n", filename.c_str()));
		return false;
	}

	// validate the number of red alert entries
	// assertion before writing so that we don't corrupt the .csg by asserting halfway through writing
	// assertion should also prevent loss of major campaign progress
	// i.e. lose one mission, not several missions worth (in theory)
	Assertion(Red_alert_wingman_status.size() <= MAX_SHIPS, "Invalid number of Red_alert_wingman_status entries: %u\n", Red_alert_wingman_status.size());

	// open it, hopefully...
	cfp = cfopen((char*)filename.c_str(), "wb", CFILE_NORMAL, CF_TYPE_PLAYERS);

	if ( !cfp ) {
		mprintf(("CSG => Unable to open '%s' for saving!\n", filename.c_str()));
		return false;
	}

	// header and version
	cfwrite_int(CSG_FILE_ID, cfp);
	cfwrite_ubyte(CSG_VERSION, cfp);

	mprintf(("CSG => Saving '%s' with version %d...\n", filename.c_str(), (int)CSG_VERSION));

	// flags and info sections go first
	mprintf(("CSG => Saving:  Flags...\n"));
	csg_write_flags();
	mprintf(("CSG => Saving:  Info...\n"));
	csg_write_info();

	// everything else is next, not order specific
	mprintf(("CSG => Saving:  Missions...\n"));
	csg_write_missions();
	mprintf(("CSG => Saving:  Techroom...\n"));
	csg_write_techroom();
	mprintf(("CSG => Saving:  Loadout...\n"));
	csg_write_loadout();
	mprintf(("CSG => Saving:  Scoring...\n"));
	csg_write_stats();
	mprintf(("CSG => Saving:  RedAlert...\n"));
	csg_write_redalert();
	mprintf(("CSG => Saving:  HUD...\n"));
	csg_write_hud();
	mprintf(("CSG => Saving:  Variables...\n"));
	csg_write_variables();
	mprintf(("CSG => Saving:  Settings...\n"));
	csg_write_settings();
	mprintf(("CSG => Saving:  Controls...\n"));
	csg_write_controls();
	mprintf(("CSG => Saving:  Cutscenes...\n"));
	csg_write_cutscenes();
	mprintf(("CSG => Saving:  Last Missions...\n"));
	csg_write_lastmissions();

	// Done!
	mprintf(("CSG => Saving complete!\n"));

	csg_close();

	return true;
}
Example #4
0
void pilotfile::csg_read_info()
{
	char t_string[NAME_LENGTH+1] = { '\0' };
	index_list_t ilist;
	int idx, list_size = 0;
	ubyte allowed = 0;

	if ( !m_have_flags ) {
		throw "Info before Flags!";
	}

	//
	// NOTE: lists may contain missing/invalid entries for current data
	//       this is not necessarily fatal
	//

	// ship list (NOTE: may contain more than MAX_SHIP_CLASSES)
	list_size = cfread_int(cfp);

	for (idx = 0; idx < list_size; idx++) {
		cfread_string_len(t_string, NAME_LENGTH, cfp);

		ilist.name = t_string;
		ilist.index = ship_info_lookup(t_string);

		ship_list.push_back(ilist);
	}

	// weapon list (NOTE: may contain more than MAX_WEAPON_TYPES)
	list_size = cfread_int(cfp);

	for (idx = 0; idx < list_size; idx++) {
		cfread_string_len(t_string, NAME_LENGTH, cfp);

		ilist.name = t_string;
		ilist.index = weapon_info_lookup(t_string);

		weapon_list.push_back(ilist);
	}

	// intel list (NOTE: may contain more than MAX_INTEL_ENTRIES)
	list_size = cfread_int(cfp);

	for (idx = 0; idx < list_size; idx++) {
		cfread_string_len(t_string, NAME_LENGTH, cfp);

		ilist.name = t_string;
		ilist.index = intel_info_lookup(t_string);

		intel_list.push_back(ilist);
	}

	// medals list (NOTE: may contain more than Num_medals)
	list_size = cfread_int(cfp);

	for (idx = 0; idx < list_size; idx++) {
		cfread_string_len(t_string, NAME_LENGTH, cfp);

		ilist.name = t_string;
		ilist.index = medals_info_lookup(t_string);

		medals_list.push_back(ilist);
	}

	// last ship flown (index into ship_list)
	idx = cfread_int(cfp);

	// check the idx is within bounds
	Assertion ((idx < (int)ship_list.size()), "Campaign file contains an incorrect value for the last flown ship class. No data in ship_list for ship number %d.", idx); 
	if (idx >= (int)ship_list.size())
		idx = -1;
	else if (idx != -1)
		p->last_ship_flown_si_index = ship_list[idx].index;
	else
		p->last_ship_flown_si_index = -1;

	// progression state
	Campaign.prev_mission = cfread_int(cfp);
	Campaign.next_mission = cfread_int(cfp);

	// loop state
	Campaign.loop_reentry = cfread_int(cfp);
	Campaign.loop_enabled = cfread_int(cfp);

	// missions completed
	Campaign.num_missions_completed = cfread_int(cfp);

	// allowed ships
	list_size = (int)ship_list.size();
	for (idx = 0; idx < list_size; idx++) {
		allowed = cfread_ubyte(cfp);

		if (allowed) {
			if (ship_list[idx].index >= 0) {
				Campaign.ships_allowed[ship_list[idx].index] = 1;
			} else {
				m_data_invalid = true;
			}
		}
	}

	// allowed weapons
	list_size = (int)weapon_list.size();
	for (idx = 0; idx < list_size; idx++) {
		allowed = cfread_ubyte(cfp);

		if (allowed) {
			if (weapon_list[idx].index >= 0) {
				Campaign.weapons_allowed[weapon_list[idx].index] = 1;
			} else {
				m_data_invalid = true;
			}
		}
	}

	if (csg_ver >= 2) {
		// single/campaign squad name & image
		cfread_string_len(p->s_squad_name, NAME_LENGTH, cfp);
		cfread_string_len(p->s_squad_filename, MAX_FILENAME_LEN, cfp);
	}

	// if anything we need/use was missing then it should be considered fatal
	if (m_data_invalid) {
		throw "Invalid data for CSG!";
	}
}
SCP_string get_frame_profile_output() {
	Assertion(frameProfiler, "Frame profiling must be enabled for this function!");

	return frameProfiler->getContent();
}
Example #6
0
/*!
 * \brief Throw a DataTransferKit::Assertion.
 *
 * \param cond A string containing the assertion condition that failed.
 *
 * \param field A string containing the file name in which the assertion
 * failed. 
 *
 * \param line The line number at which the assertion failed.
 */
void throwAssertion( const std::string& cond, const std::string& file,
		     const int line )
{
    throw Assertion( cond, file, line );
}
Example #7
0
int batch_add_polygon(int texture, int tmap_flags, vec3d *pos, matrix *orient, float width, float height, float alpha)
{
	//idiot-proof
	if(width == 0 || height == 0)
		return 0;

	Assert(pos != NULL);
	Assert(orient != NULL);

	//Let's begin.

	const int NUM_VERTICES = 4;
	vec3d p[NUM_VERTICES] = { ZERO_VECTOR };
	vertex v[NUM_VERTICES] = { vertex() };

	p[0].xyz.x = width;
	p[0].xyz.y = height;

	p[1].xyz.x = -width;
	p[1].xyz.y = height;

	p[2].xyz.x = -width;
	p[2].xyz.y = -height;

	p[3].xyz.x = width;
	p[3].xyz.y = -height;

	for(int i = 0; i < NUM_VERTICES; i++)
	{
		vec3d tmp = vmd_zero_vector;

		//Rotate correctly
		vm_vec_unrotate(&tmp, &p[i], orient);
		//Move to point in space
		vm_vec_add2(&tmp, pos);

		//Convert to vertex
		g3_transfer_vertex(&v[i], &tmp);
	}

	v[0].texture_position.u = 1.0f;
	v[0].texture_position.v = 0.0f;

	v[1].texture_position.u = 0.0f;
	v[1].texture_position.v = 0.0f;

	v[2].texture_position.u = 0.0f;
	v[2].texture_position.v = 1.0f;

	v[3].texture_position.u = 1.0f;
	v[3].texture_position.v = 1.0f;

	if (texture < 0) {
		Int3();
		return 1;
	}

	batch_item *item = NULL;

	SCP_map<int, batch_item>::iterator it = geometry_map.find(texture);

	if ( !geometry_map.empty() && it != geometry_map.end() ) {
		item = &it->second;
	} else {
		item = &geometry_map[texture];
		item->texture = texture;
	}

	Assertion( (item->laser == false), "Particle effect %s used as laser glow or laser bitmap\n", bm_get_filename(texture) );

	item->tmap_flags = tmap_flags;
	item->alpha = alpha;

	item->batch.add_allocate(1);

	item->batch.draw_quad(v);

	return 0;
}
void frame_profile_process_frame() {
	Assertion(frameProfiler, "Frame profiling must be enabled for this function!");

	return frameProfiler->processFrame();
}
// loads nframes bitmaps, starting at index start_frame.
// anything < start_frame will not be loaded.
// this keeps the loading code from trying to load bitmaps which don't exist
// and taking an unnecessary disk hit.		
int UI_GADGET::set_bmaps(char *ani_fname, int nframes, int start_frame)
{
	int first_frame, i;	
	char full_name[MAX_FILENAME_LEN] = "";
	char tmp[33];
	int idx, s_idx;	
	int num_digits;
	int its_all_good = 0;
	
	// clear out all frames
	for(idx=0; idx<MAX_BMAPS_PER_GADGET; idx++){
		bmap_ids[idx] = -1;
	}
	
	// load all the bitmaps
	bm_filename = ani_fname;

	Assertion(nframes < MAX_BMAPS_PER_GADGET, "Too many frames specified (%d), must be less than MAX_BMAPS_PER_GADGET", nframes);
	m_num_frames = nframes;
	for(idx=start_frame; idx<nframes; idx++){
		// clear the string
		strcpy_s(full_name, "");

		// get the # of digits for this index
		num_digits = (idx < 10) ? 1 : (idx < 100) ? 2 : (idx < 1000) ? 3 : 4;

		// build the actual filename
		strcpy_s(full_name, ani_fname);
		for(s_idx=0; s_idx<(4-num_digits); s_idx++){
			strcat_s(full_name, NOX("0"));
		}
		sprintf(tmp, "%d", idx);
		strcat_s(full_name, tmp);

		// try and load the bitmap				
		bmap_ids[idx] = bm_load(full_name);	
		if(bmap_ids[idx] != -1){		
			
			its_all_good = 1;
		} 
	}

	// done
	if(its_all_good){
		uses_bmaps = 1;		
		return 0;
	}

	// no go, so try and load as an ani. try and load as an .ani	
	first_frame = bm_load_animation(ani_fname, &m_num_frames);	
	if((first_frame >= 0) && (m_num_frames <= MAX_BMAPS_PER_GADGET)){					
		// seems pretty stupid that we didn't just use a variable for the first frame and access all
		// other frames offset from it instead of accessing this bmap_ids[] array, but probably too
		// much trouble to go through and change this anymore.  How sad..
		for ( i=0; i<m_num_frames; i++ ) {
			bmap_ids[i] = first_frame + i;
		}	
	}	

	// flag that this control is using bitmaps for art	
	uses_bmaps = 1;
	return 0;
}
Example #10
0
void credits_parse_table(const char* filename)
{	
	try
	{
		read_file_text(filename, CF_TYPE_TABLES);
		reset_parse();

		// any metadata?
		if (optional_string("$Music:"))
		{
			stuff_string(Credits_music_name, F_NAME, NAME_LENGTH);
		}
		if (optional_string("$Number of Images:"))
		{
			int temp;
			stuff_int(&temp);
			if (temp > 0)
				Credits_num_images = temp;
		}
		if (optional_string("$Start Image Index:"))
		{
			stuff_int(&Credits_artwork_index);

			// bounds check
			if (Credits_artwork_index < 0)
			{
				Credits_artwork_index = 0;
			}
			else if (Credits_artwork_index >= Credits_num_images)
			{
				Credits_artwork_index = Credits_num_images - 1;
			}
		}
		if (optional_string("$Text scroll rate:"))
		{
			stuff_float(&Credits_scroll_rate);
			if (Credits_scroll_rate < 0.01f)
				Credits_scroll_rate = 0.01f;
		}
		if (optional_string("$Artworks display time:"))
		{
			stuff_float(&Credits_artwork_display_time);
			if (Credits_artwork_display_time < 0.01f)
				Credits_artwork_display_time = 0.01f;
		}
		if (optional_string("$Artworks fade time:"))
		{
			stuff_float(&Credits_artwork_fade_time);
			if (Credits_artwork_fade_time < 0.01f)
				Credits_artwork_fade_time = 0.01f;
		}
		if (optional_string("$SCP Credits position:"))
		{
			char mode[NAME_LENGTH];

			stuff_string(mode, F_NAME, NAME_LENGTH);

			if (!stricmp(mode, "Start"))
				SCP_credits_position = START;
			else if (!stricmp(mode, "End"))
				SCP_credits_position = END;
			else
				Warning(LOCATION, "Unknown credits position mode \"%s\".", mode);
		}

		ignore_white_space();

		SCP_string credits_text;
		SCP_string line;

		SCP_vector<int> charNum;
		SCP_vector<const char*> lines;
		int numLines = -1;

		bool first_run = true;
		while (!check_for_eof_raw() && !check_for_string_raw("#end"))
		{
			// Read in a line of text			
			stuff_string_line(line);

			// This is a bit odd but it means if a total conversion uses different credits the 
			// Volition credit won't happen
			// Also don't append the default credits anymore when there was already a parsed table
			if (first_run && !Credits_parsed && !line.compare(mod_check))
			{
				credits_text.append(unmodified_credits);
			}

			first_run = false;

			if (line.empty())
			{
				// If the line is empty then just append a newline, don't bother with splitting it first
				credits_text.append("\n");
			}
			else
			{
				// split_str doesn't take care of this.
				charNum.clear();

				// Split the string into multiple lines if it's too long
				numLines = split_str(line.c_str(), Credits_text_coords[gr_screen.res][2], charNum, lines, -1);

				// Make sure that we have valid data
				Assertion(lines.size() == (size_t)numLines, "split_str reported %d lines but vector contains " SIZE_T_ARG " entries!", numLines, lines.size());

				Assertion(lines.size() <= charNum.size(),
					"Something has gone wrong while splitting strings. Got " SIZE_T_ARG " lines but only " SIZE_T_ARG " chacter lengths.",
					lines.size(), charNum.size());

				// Now add all splitted lines to the credit text and append a newline to the end
				for (int i = 0; i < numLines; i++)
				{
					credits_text.append(SCP_string(lines[i], charNum[i]));
					credits_text.append("\n");
				}
			}
		}

		Credit_text_parts.push_back(credits_text);

		Credits_parsed = true;
	}
	catch (const parse::ParseException& e)
	{
		mprintf(("TABLES: Unable to parse '%s'!  Error message = %s.\n", filename, e.what()));
		return;
	}
}
// ********************************************************************************************
// Engages autopilot
// This does:
//        * Control switched from player to AI
//        * Time compression to 32x
//        * Lock time compression -WMC
//        * Tell AI to fly to targeted Nav Point (for all nav-status wings/ships)
//		  * Sets max waypoint speed to the best-speed of the slowest ship tagged
bool StartAutopilot()
{
	// Check for support ship and dismiss it if it is not doing anything.
	// If the support ship is doing something then tell the user such.
	for ( object *objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) )
	{
		if ((objp->type == OBJ_SHIP) && !(objp->flags & OF_SHOULD_BE_DEAD))
		{
			Assertion((objp->instance >= 0) && (objp->instance < MAX_SHIPS),
				"objp does not have a valid pointer to a ship. Pointer is %d, which is smaller than 0 or bigger than %d",
				objp->instance, MAX_SHIPS);
			ship *shipp = &Ships[objp->instance];

			if (shipp->team != Player_ship->team)
				continue;

			Assertion((shipp->ship_info_index >= 0) && (shipp->ship_info_index < MAX_SHIP_CLASSES),
				"Ship '%s' does not have a valid pointer to a ship class. Pointer is %d, which is smaller than 0 or bigger than %d",
				shipp->ship_name, shipp->ship_info_index, MAX_SHIP_CLASSES);
			ship_info *sip = &Ship_info[shipp->ship_info_index];

			if ( !(sip->flags & SIF_SUPPORT) )
				continue;

			// don't deal with dying or departing support ships
			if ( shipp->flags & (SF_DYING | SF_DEPARTING) )
				continue;

			Assert(shipp->ai_index != -1);
			ai_info* support_ship_aip = &(Ai_info[Ships[objp->instance].ai_index]);

			// is support ship trying to rearm-repair
			if ( ai_find_goal_index( support_ship_aip->goals, AI_GOAL_REARM_REPAIR ) == -1 ) {
				// no, so tell it to depart
				ai_add_ship_goal_player( AIG_TYPE_PLAYER_SHIP, AI_GOAL_WARP, -1, NULL, support_ship_aip );
			} else {
				// yes
				send_autopilot_msgID(NP_MSG_FAIL_SUPPORT_WORKING);
				return false;
			}
		}
	}
	if (!CanAutopilot())
		return false;

	AutoPilotEngaged = true;

	// find the ship that is "leading" all of the ships when the player starts
	// autopilot
	// by default the ship that is leading the autopilot session the player's
	// wing leader (if the player is the wing leader then it will be the
	// player).
	// TODO:implement a way to allow a FREDer to say a different ship is leader
	Autopilot_flight_leader = get_wing_leader(Player_ship->wingnum);
	if ( Autopilot_flight_leader == NULL ) {
		// force player to be the leader if he doesn't have a wing
		Autopilot_flight_leader = Player_obj;
	}

	if (The_mission.flags & MISSION_FLAG_USE_AP_CINEMATICS)
		LockAPConv = timestamp(); // lock convergence instantly
	else
		LockAPConv = timestamp(3000); // 3 seconds before we lock convergence
	Player_use_ai = 1;
	set_time_compression(1);
	lock_time_compression(true);

	// determine speed cap
	int i,j, wcount=1, tc_factor=1;
	float speed_cap = 1000000.0; // 1m is a safe starting point
	float radius = Player_obj->radius, distance = 0.0f, ftemp;
	bool capshipPresent = false;
	int capship_counts[3]; // three size classes
	capship_counts[0] = 0;
	capship_counts[1] = 0;
	capship_counts[2] = 0;

	int capship_placed[3]; // three size classes
	capship_placed[0] = 0;
	capship_placed[1] = 0;
	capship_placed[2] = 0;

	float capship_spreads[3];
	capship_spreads[0] = 0.0f;
	capship_spreads[1] = 0.0f;
	capship_spreads[2] = 0.0f;

	SCP_vector<int> capIndexes;

	// empty the autopilot wings map
	autopilot_wings.clear();

	// vars for usage w/ cinematic
	vec3d pos, norm1, perp, tpos, rpos = Player_obj->pos, zero;
	memset(&zero, 0, sizeof(vec3d));


	// instantly turn player toward tpos
	if (The_mission.flags & MISSION_FLAG_USE_AP_CINEMATICS)
	{
		vm_vec_sub(&norm1, Navs[CurrentNav].GetPosition(), &Player_obj->pos);
		vm_vector_2_matrix(&Player_obj->orient, &norm1, NULL, NULL);
	}

	for (i = 0; i < MAX_SHIPS; i++)
	{
		if (Ships[i].objnum != -1 && 
				(Ships[i].flags2 & SF2_NAVPOINT_CARRY || 
					(Ships[i].wingnum != -1 && Wings[Ships[i].wingnum].flags & WF_NAV_CARRY)
				)
			)
		{
			if (speed_cap > vm_vec_mag(&Ship_info[Ships[i].ship_info_index].max_vel))
				speed_cap = vm_vec_mag(&Ship_info[Ships[i].ship_info_index].max_vel);
		}
	}

	// damp speed_cap to 90% of actual -- to make sure ships stay in formation
	if (The_mission.flags & MISSION_FLAG_USE_AP_CINEMATICS)
		speed_cap = 0.90f * speed_cap;

	if ( speed_cap < 1.0f ) {
		/* We need to deal with this so that incorrectly flagged ships will not
		cause the engine to fail to limit all the ships speeds correctly. */
		Warning(LOCATION, "Ship speed cap is way too small (%f)!\n"
			"This is normally caused by a ship that has nav-carry-status set, but cannot move itself (like a Cargo container).\n"
			"Speed cap has been set to 1.0 m/s.",
			speed_cap);
		speed_cap = 1.0f;
	}

	ramp_bias = speed_cap/50.0f;

	// assign ship goals
	// when assigning goals to individual ships only do so if Ships[shipnum].wingnum != -1 
	// we will assign wing goals below

	for (i = 0; i < MAX_SHIPS; i++)
	{
		if (Ships[i].objnum != -1 && 
				(Ships[i].flags2 & SF2_NAVPOINT_CARRY || 
					(Ships[i].wingnum != -1 && Wings[Ships[i].wingnum].flags & WF_NAV_CARRY)
				)
			)
		{
			// do we have capital ships in the area?
			if (Ship_info[Ships[i].ship_info_index].flags 
				& ( SIF_CRUISER | SIF_CAPITAL | SIF_SUPERCAP | SIF_CORVETTE | SIF_AWACS | SIF_GAS_MINER | SIF_FREIGHTER | SIF_TRANSPORT))
			{
				capshipPresent = true;

				capIndexes.push_back(i);
				// ok.. what size class

				if (Ship_info[Ships[i].ship_info_index].flags & (SIF_CAPITAL | SIF_SUPERCAP))
				{
					capship_counts[0]++;
					if (capship_spreads[0] < Objects[Ships[i].objnum].radius)
						capship_spreads[0] = Objects[Ships[i].objnum].radius;
				}
				else if (Ship_info[Ships[i].ship_info_index].flags & (SIF_CORVETTE))
				{
					capship_counts[1]++;
					if (capship_spreads[1] < Objects[Ships[i].objnum].radius)
						capship_spreads[1] = Objects[Ships[i].objnum].radius;
				}
				else
				{
					capship_counts[2]++;
					if (capship_spreads[2] < Objects[Ships[i].objnum].radius)
						capship_spreads[2] = Objects[Ships[i].objnum].radius;
				}
			}



			// check for bigger radius for usage later
			/*if (!vm_vec_cmp(&rpos, &Player_obj->pos)) 
				// want to make sure rpos isn't player pos - we can worry about it being largest object's later
			{
				rpos = Objects[Ships[i].objnum].pos;
			}*/

			if (Objects[Ships[i].objnum].radius > radius)
			{
				rpos = Objects[Ships[i].objnum].pos;
				radius = Objects[Ships[i].objnum].radius;
			}

			if (The_mission.flags & MISSION_FLAG_USE_AP_CINEMATICS)
			{// instantly turn the ship to match the direction player is looking
				//vm_vec_sub(&norm1, Navs[CurrentNav].GetPosition(), &Player_obj->pos);
				vm_vector_2_matrix(&Objects[Ships[i].objnum].orient, &norm1, NULL, NULL);
			}

			// snap wings into formation
			if (The_mission.flags & MISSION_FLAG_USE_AP_CINEMATICS &&  // only if using cinematics 
				(Ships[i].wingnum != -1 && Wings[Ships[i].wingnum].flags & WF_NAV_CARRY) // only if in a wing
				&& Autopilot_flight_leader != &Objects[Ships[i].objnum]) //only if not flight leader's object
			{	
				ai_info	*aip = &Ai_info[Ships[i].ai_index];
				int wingnum = aip->wing, wing_index = get_wing_index(&Objects[Ships[i].objnum], wingnum);
				vec3d goal_point;
				object *leader_objp = get_wing_leader(wingnum);
				
				if (leader_objp != &Objects[Ships[i].objnum])
				{
					// not leader.. get our position relative to leader
					get_absolute_wing_pos_autopilot(&goal_point, leader_objp, wing_index, aip->ai_flags & AIF_FORMATION_OBJECT);
				}
				else
				{
					ai_clear_wing_goals(wingnum);
					j = 1+int( (float)floor(double(wcount-1)/2.0) );
					switch (wcount % 2)
					{
						case 1: // back-left
							vm_vec_add(&perp, &zero, &Autopilot_flight_leader->orient.vec.rvec);
							//vm_vec_sub(&perp, &perp, &Player_obj->orient.vec.fvec);
							vm_vec_normalize(&perp);
							vm_vec_scale(&perp, -166.0f*j); // 166m is supposedly the optimal range according to tolwyn
							vm_vec_add(&goal_point, &Autopilot_flight_leader->pos, &perp);
							break;

						default: //back-right
						case 0:
							vm_vec_add(&perp, &zero, &Autopilot_flight_leader->orient.vec.rvec);
							//vm_vec_sub(&perp, &perp, &Player_obj->orient.vec.fvec);
							vm_vec_normalize(&perp);
							vm_vec_scale(&perp, 166.0f*j);
							vm_vec_add(&goal_point, &Autopilot_flight_leader->pos, &perp);
							break;
					}
					autopilot_wings[wingnum] = wcount;
					wcount++;
				}
				Objects[Ships[i].objnum].pos = goal_point;			
				if (vm_vec_dist_quick(&Autopilot_flight_leader->pos, &Objects[Ships[i].objnum].pos) > distance)
				{
					distance = vm_vec_dist_quick(&Autopilot_flight_leader->pos, &Objects[Ships[i].objnum].pos);
				}
			}
			// lock primary and secondary weapons
			if ( LockWeaponsDuringAutopilot )
				Ships[i].flags2 |= (SF2_PRIMARIES_LOCKED | SF2_SECONDARIES_LOCKED);

			// clear the ship goals and cap the waypoint speed
			ai_clear_ship_goals(&Ai_info[Ships[i].ai_index]);
			Ai_info[Ships[i].ai_index].waypoint_speed_cap = (int)speed_cap;

			
			// if they're not part of a wing set their goal
			if (Ships[i].wingnum == -1 || The_mission.flags & MISSION_FLAG_USE_AP_CINEMATICS)
			{ 
				if (Navs[CurrentNav].flags & NP_WAYPOINT)
				{
					ai_add_ship_goal_player( AIG_TYPE_PLAYER_SHIP, AI_GOAL_WAYPOINTS_ONCE, 0, ((waypoint_list*)Navs[CurrentNav].target_obj)->get_name(), &Ai_info[Ships[i].ai_index] );
					//fixup has to wait until after wing goals
				}
				else
				{
					ai_add_ship_goal_player( AIG_TYPE_PLAYER_SHIP, AI_GOAL_FLY_TO_SHIP, 0, ((ship*)Navs[CurrentNav].target_obj)->ship_name, &Ai_info[Ships[i].ai_index] );
				}

			}
		}
	}

	// assign wing goals
	if (!(The_mission.flags & MISSION_FLAG_USE_AP_CINEMATICS))
	{
		for (i = 0; i < MAX_WINGS; i++)
		{
			if (Wings[i].flags & WF_NAV_CARRY )
			{	
				//ai_add_ship_goal_player( int type, int mode, int submode, char *shipname, ai_info *aip );

				//ai_add_wing_goal_player( AIG_TYPE_PLAYER_WING, AI_GOAL_STAY_NEAR_SHIP, 0, target_shipname, wingnum );
				//ai_add_wing_goal_player( AIG_TYPE_PLAYER_WING, AI_GOAL_WAYPOINTS_ONCE, 0, target_shipname, wingnum );
				//ai_clear_ship_goals( &(Ai_info[Ships[num].ai_index]) );
				
				ai_clear_wing_goals( i );
				if (Navs[CurrentNav].flags & NP_WAYPOINT)
				{
					
					ai_add_wing_goal_player( AIG_TYPE_PLAYER_WING, AI_GOAL_WAYPOINTS_ONCE, 0, ((waypoint_list*)Navs[CurrentNav].target_obj)->get_name(), i );

					// "fix up" the goal
					for (j = 0; j < MAX_AI_GOALS; j++)
					{
						if (Wings[i].ai_goals[j].ai_mode == AI_GOAL_WAYPOINTS_ONCE ||
							Wings[i].ai_goals[j].ai_mode == AIM_WAYPOINTS )
						{
							autopilot_ai_waypoint_goal_fixup(&(Wings[i].ai_goals[j]));
						}
					}
				}
				else
				{
					ai_add_wing_goal_player( AIG_TYPE_PLAYER_WING, AI_GOAL_FLY_TO_SHIP, 0, ((ship*)Navs[CurrentNav].target_obj)->ship_name, i );

				}
			}
		}
	}

	// fixup has to go down here because ships are assigned goals during wing goals as well
	for (i = 0; i < MAX_SHIPS; i++)
	{
		if (Ships[i].objnum != -1)
		{
			if (Ships[i].flags2 & SF2_NAVPOINT_CARRY || 
				(Ships[i].wingnum != -1 && Wings[Ships[i].wingnum].flags & WF_NAV_CARRY))
				for (j = 0; j < MAX_AI_GOALS; j++)
				{
					if (Ai_info[Ships[i].ai_index].goals[j].ai_mode == AI_GOAL_WAYPOINTS_ONCE ||
						Ai_info[Ships[i].ai_index].goals[j].ai_mode == AIM_WAYPOINTS)
					{
						autopilot_ai_waypoint_goal_fixup( &(Ai_info[Ships[i].ai_index].goals[j]) );

						
						// formation fixup
						//ai_form_on_wing(&Objects[Ships[i].objnum], &Objects[Player_ship->objnum]);
					}
				}
		}
	}
	start_dist = DistanceTo(CurrentNav);

	// ----------------------------- setup cinematic -----------------------------
	if (The_mission.flags & MISSION_FLAG_USE_AP_CINEMATICS)
	{	
		if (capshipPresent)
		{
			// position capships

			vec3d right, front, up, offset;
			for (SCP_vector<int>::iterator idx = capIndexes.begin(); idx != capIndexes.end(); ++idx)
			{
				vm_vec_add(&right, &Autopilot_flight_leader->orient.vec.rvec, &zero);
				vm_vec_add(&front, &Autopilot_flight_leader->orient.vec.fvec, &zero);
				vm_vec_add(&up, &Autopilot_flight_leader->orient.vec.uvec, &zero);
				vm_vec_add(&offset, &zero, &zero);
				if (Ship_info[Ships[*idx].ship_info_index].flags & (SIF_CAPITAL | SIF_SUPERCAP))
				{
					//0 - below - three lines of position

					// front/back to zero
					vm_vec_add(&front, &zero, &zero);

					// position below
					vm_vec_scale(&up, capship_spreads[0]); // scale the up vector by the radius of the largest ship in this formation part


					switch (capship_placed[0] % 3)
					{
						case 1: // right
							vm_vec_scale(&right, capship_spreads[0]);
							break;
							
						case 2: // left
							vm_vec_scale(&right, -capship_spreads[0]);
							break;

						default: // straight
						case 0:
							vm_vec_add(&right, &zero, &zero);
							vm_vec_scale(&up, 1.5); // add an extra half-radius
							break;
					}
		
					// scale by  row
					vm_vec_scale(&right, (1+((float)floor((float)capship_placed[0]/3)))); 
					vm_vec_scale(&up, -(1+((float)floor((float)capship_placed[0]/3))));

					capship_placed[0]++;
				}
				else if (Ship_info[Ships[*idx].ship_info_index].flags & SIF_CORVETTE)
				{
					//1 above - 3 lines of position
					// front/back to zero
					vm_vec_add(&front, &zero, &zero);

					// position below
					vm_vec_scale(&up, capship_spreads[1]); // scale the up vector by the radius of the largest ship in this formation part


					switch (capship_placed[1] % 3)
					{
						case 1: // right
							vm_vec_scale(&right, capship_spreads[1]); 
							break;
							
						case 2: // left
							vm_vec_scale(&right, -capship_spreads[1]); 
							break;

						default: // straight
						case 0:
							vm_vec_add(&right, &zero, &zero);
							vm_vec_scale(&up, 1.5); // add an extra half-radius
							break;
					}
		
					// scale by  row
					vm_vec_scale(&right, (1+((float)floor((float)capship_placed[1]/3)))); 
					vm_vec_scale(&up, (1+((float)floor((float)capship_placed[1]/3))));

					// move ourselves up and out of the way of the smaller ships
					vm_vec_add(&perp, &Autopilot_flight_leader->orient.vec.uvec, &zero);
					vm_vec_scale(&perp, capship_spreads[2]);
					vm_vec_add(&up, &up, &perp);

					capship_placed[1]++;
				}
				else
				{
					//2 either side - 6 lines of position (right (dir, front, back), left (dir, front, back) )
					// placing pattern: right, left, front right, front left, rear right, rear left

					// up/down to zero
					vm_vec_add(&up, &zero, &zero);


					switch (capship_placed[2] % 6)
					{
						case 5:  // rear left
							vm_vec_scale(&right, -capship_spreads[2]);
							vm_vec_scale(&front, -capship_spreads[2]); 
							break;

						case 4:  // rear right
							vm_vec_scale(&right, capship_spreads[2]); 
							vm_vec_scale(&front, -capship_spreads[2]); 
							break;

						case 3:  // front left
							vm_vec_scale(&right, -capship_spreads[2]); 
							vm_vec_scale(&front, capship_spreads[2]); 
							break;

						case 2:  // front right
							vm_vec_scale(&right, capship_spreads[2]); 
							vm_vec_scale(&front, capship_spreads[2]);
							break;

						case 1:  // straight left
							vm_vec_scale(&right, 1.5);
							vm_vec_scale(&right, -capship_spreads[2]);
							vm_vec_add(&front, &zero, &zero);
							break;

						default: // straight right
						case 0:
							vm_vec_scale(&right, 1.5);
							vm_vec_scale(&right, capship_spreads[2]);
							vm_vec_add(&front, &zero, &zero);
							break;
					}
					// these ships seem to pack a little too tightly
					vm_vec_scale(&right, 2*(1+((float)floor((float)capship_placed[2]/3)))); 
					vm_vec_scale(&front, 2*(1+((float)floor((float)capship_placed[2]/3))));

					// move "out" by 166*(wcount-1) so we don't bump into fighters
					vm_vec_add(&perp, &Autopilot_flight_leader->orient.vec.rvec, &zero);
					vm_vec_scale(&perp, 166.0f*float(wcount-1));
					if ( (capship_placed[2] % 2) == 0)
						vm_vec_add(&right, &right, &perp);
					else
						vm_vec_sub(&right, &right, &perp);

					capship_placed[2]++;
				}

				// integrate the up/down componant
				vm_vec_add(&offset, &offset, &up);

				//integrate the left/right componant
				vm_vec_add(&offset, &offset, &right);

				//integrate the left/right componant
				vm_vec_add(&offset, &offset, &front);

				// global scale the position by 50%
				//vm_vec_scale(&offset, 1.5);

				vm_vec_add(&Objects[Ships[*idx].objnum].pos, &Autopilot_flight_leader->pos, &offset);

				if (vm_vec_dist_quick(&Autopilot_flight_leader->pos, &Objects[Ships[*idx].objnum].pos) > distance)
				{
					distance = vm_vec_dist_quick(&Autopilot_flight_leader->pos, &Objects[Ships[*idx].objnum].pos);
				}
			}
		}

		ftemp = floor(Autopilot_flight_leader->phys_info.max_vel.xyz.z/speed_cap);
		if (ftemp >= 2.0f && ftemp < 4.0f)
			tc_factor = 2;
		else if (ftemp >= 4.0f && ftemp < 8.0f)
			tc_factor = 4;
		else if (ftemp >= 8.0f)
			tc_factor = 8;



		tpos = *Navs[CurrentNav].GetPosition();
		// determine distance toward nav at which camera will be
		vm_vec_sub(&pos, &tpos, &Autopilot_flight_leader->pos);
		vm_vec_normalize(&pos); // pos is now a unit vector in the direction we will be moving the camera
		//norm1 = pos;
		vm_vec_scale(&pos, 5*speed_cap*tc_factor); // pos is now scaled by 5 times the speed (5 seconds ahead)
		vm_vec_add(&pos, &pos, &Autopilot_flight_leader->pos); // pos is now 5*speed cap in front of player ship

		switch (myrand()%24) 
		// 8 different ways of getting perp points
		// 4 of which will not be used when capships are present (anything below, or straight above)
		{

			case 1: // down
			case 9:
			case 16:
				if (capship_placed[0] == 0)
					vm_vec_sub(&perp, &zero, &Autopilot_flight_leader->orient.vec.uvec);
				else
				{	// become up-left
					vm_vec_add(&perp, &zero, &Autopilot_flight_leader->orient.vec.uvec);
					vm_vec_sub(&perp, &perp, &Autopilot_flight_leader->orient.vec.rvec);
				}
				break;

			case 2: // up
			case 10:
			case 23:
				vm_vec_add(&perp, &perp, &Autopilot_flight_leader->orient.vec.uvec);
				if (capshipPresent) // become up-right
					vm_vec_add(&perp, &perp, &Autopilot_flight_leader->orient.vec.rvec);
				break;

			case 3: // left
			case 11:
			case 22:
				vm_vec_sub(&perp, &zero, &Autopilot_flight_leader->orient.vec.rvec);
				break;

			case 4: // up-left
			case 12:
			case 21:
				vm_vec_sub(&perp, &zero, &Autopilot_flight_leader->orient.vec.rvec);
				vm_vec_add(&perp, &perp, &Autopilot_flight_leader->orient.vec.uvec);
				break;

			case 5: // up-right
			case 13:
			case 20:
				vm_vec_add(&perp, &zero, &Autopilot_flight_leader->orient.vec.rvec);
				vm_vec_add(&perp, &perp, &Autopilot_flight_leader->orient.vec.uvec);
				break;

			case 6: // down-left
			case 14:
			case 19:
				vm_vec_sub(&perp, &zero, &Autopilot_flight_leader->orient.vec.rvec);
				if (capship_placed[0] < 2)
					vm_vec_sub(&perp, &perp, &Autopilot_flight_leader->orient.vec.uvec);
				else
					vm_vec_add(&perp, &perp, &Autopilot_flight_leader->orient.vec.uvec);
				break;

			case 7: // down-right
			case 15:
			case 18:
				vm_vec_add(&perp, &zero, &Autopilot_flight_leader->orient.vec.rvec);
				if (capship_placed[0] < 1)
					vm_vec_sub(&perp, &perp, &Autopilot_flight_leader->orient.vec.uvec);
				else
					vm_vec_add(&perp, &perp, &Autopilot_flight_leader->orient.vec.uvec);
				break;

			default:
			case 0: // right
			case 8:
			case 17:
				perp = Autopilot_flight_leader->orient.vec.rvec;
				break;

		}
		vm_vec_normalize(&perp);
		//vm_vec_scale(&perp, 2*radius+distance);

		vm_vec_scale(&perp,  Autopilot_flight_leader->radius+radius);

		// randomly scale up/down by up to 20%
		j = 20-myrand()%40; // [-20,20]

		vm_vec_scale(&perp, 1.0f+(float(j)/100.0f));
		vm_vec_add(&cameraPos, &pos, &perp);

		if (capshipPresent)
		{
			vm_vec_normalize(&perp);

			// place it behind
			//vm_vec_copy_scale(&norm1, &Player_obj->orient.vec.fvec, -2*(Player_obj->radius+radius*(1.0f+(float(j)/100.0f))));
			//vm_vec_add(&cameraTarget, &cameraTarget, &norm1);

			vm_vec_copy_scale(&cameraTarget,&perp, radius/5.0f);

			//vm_vec_scale(&cameraTarget, Player_obj->radius+radius*(1.0f+(float(j)/100.0f)));

			//vm_vec_add(&cameraTarget, &pos, &cameraTarget);
			//CameraSpeed = (radius+distance)/25;

			//vm_vec_add(&cameraTarget, &zero, &perp);
			//vm_vec_scale(&CameraVelocity, (radius+distance/100.f));
			//vm_vec_scale(&CameraVelocity, 1.0f/float(NPS_TICKRATE*tc_factor));
		}
		else
		{
			vm_vec_add(&cameraTarget, &zero, &zero);
			//CameraSpeed = 0;
		}
		//CameraMoving = false;


		EndAPCinematic = timestamp((10000*tc_factor)+NPS_TICKRATE); // 10 objective seconds before end of cinematic 
		MoveCamera = timestamp((5500*tc_factor)+NPS_TICKRATE);
		camMovingTime = int(4.5*float(tc_factor));
		set_time_compression((float)tc_factor);
	}

	return true;
}
void fs2netd_options_config_init()
{
	if (PXO_options_loaded) {
		return;
	}

	if ( !strlen(Multi_options_g.game_tracker_ip) ) {
		ml_printf("NOTICE: Address for game tracker not specified, using default instead (%s).", FS2NETD_DEFAULT_SERVER);
		strncpy( Multi_options_g.game_tracker_ip, FS2NETD_DEFAULT_SERVER, MULTI_OPTIONS_STRING_LEN );
	} else if ( !strcmp("gt.pxo.net", Multi_options_g.game_tracker_ip) ) {
		ml_printf("NOTICE: Incompatible game tracker IP detected (gt.pxo.net), using default instead (%s)!", FS2NETD_DEFAULT_SERVER);
		strncpy( Multi_options_g.game_tracker_ip, FS2NETD_DEFAULT_SERVER, MULTI_OPTIONS_STRING_LEN );
	}

	if ( !strlen(Multi_options_g.user_tracker_ip) ) {
		ml_printf("NOTICE: Address for user tracker not specified, using default instead (%s).", FS2NETD_DEFAULT_SERVER);
		strncpy( Multi_options_g.user_tracker_ip, FS2NETD_DEFAULT_SERVER, MULTI_OPTIONS_STRING_LEN );
	} else if ( !strcmp("ut.pxo.net", Multi_options_g.user_tracker_ip) ) {
		ml_printf("NOTICE: Incompatible user tracker IP detected (ut.pxo.net), using default instead (%s)!", FS2NETD_DEFAULT_SERVER);
		strncpy( Multi_options_g.user_tracker_ip, FS2NETD_DEFAULT_SERVER, MULTI_OPTIONS_STRING_LEN );
	}

	if ( !strlen(Multi_options_g.tracker_port) ) {
		if ( FS2NetD_port >= 1024 && FS2NetD_port <= USHRT_MAX ) {
			ml_printf("NOTICE: User override for game/user tracker port not specified, using game_settings.tbl override (%i).", FS2NetD_port);
			int result;
			result = sprintf(Multi_options_g.tracker_port, "%i", FS2NetD_port);
			Assertion( result > 0, "Copying port %i to tracker_port failed\n", FS2NetD_port );
		}
		else {
			if ( FS2NetD_port != 0 ) {
				ml_printf("ERROR: game_settings.tbl override for game/user tracker port '%i' must be between %i and %i.", FS2NetD_port, 1024, USHRT_MAX);
			}
			ml_printf("NOTICE: Port for game/user trackers not specified, using default instead (%s).", FS2NETD_DEFAULT_PORT);
			strncpy( Multi_options_g.tracker_port, FS2NETD_DEFAULT_PORT, STD_NAME_LEN );
		}
	} else {
		long port_tmp = strtol(Multi_options_g.tracker_port, (char**)NULL, 10);

		if ( (port_tmp < 1024) || (port_tmp > USHRT_MAX) ) {
			ml_printf("NOTICE: The port specified for game/user trackers, '%ld', is outside of the required range, %i through %i!", port_tmp, 1024, USHRT_MAX);
			ml_printf("NOTICE: Port for game/user trackers is invalid, using default instead (%s).", FS2NETD_DEFAULT_PORT);
			strncpy( Multi_options_g.tracker_port, FS2NETD_DEFAULT_PORT, STD_NAME_LEN );
		}
	}

	if ( !strlen(Multi_options_g.pxo_ip) ) {
		ml_printf("NOTICE: Address for chat server not specified, using default instead (%s).", FS2NETD_DEFAULT_CHAT_SERVER);
		strncpy( Multi_options_g.pxo_ip, FS2NETD_DEFAULT_CHAT_SERVER, MULTI_OPTIONS_STRING_LEN );
	} else if ( !strcmp("chat.pxo.net", Multi_options_g.pxo_ip) ) {
		ml_printf("NOTICE: Incompatible chat server IP detected (chat.pxo.net), using default instead (%s)!", FS2NETD_DEFAULT_CHAT_SERVER);
		strncpy( Multi_options_g.pxo_ip, FS2NETD_DEFAULT_CHAT_SERVER, MULTI_OPTIONS_STRING_LEN );
	}

	if ( !strlen(Multi_options_g.pxo_banner_url) ) {
		ml_printf("NOTICE: URL for banners not specified, using default instead (%s).", FS2NETD_DEFAULT_BANNER_URL);
		strncpy( Multi_options_g.pxo_banner_url, FS2NETD_DEFAULT_BANNER_URL, MULTI_OPTIONS_STRING_LEN );
	} else if ( !strcmp("http://www.pxo.net/files/banners", Multi_options_g.pxo_banner_url) ) {
		ml_printf("NOTICE: Incompatible banner URL detected (chat.pxo.net), using default instead (%s)!", FS2NETD_DEFAULT_BANNER_URL);
		strncpy( Multi_options_g.pxo_banner_url, FS2NETD_DEFAULT_BANNER_URL, MULTI_OPTIONS_STRING_LEN );
	}

	PXO_options_loaded = true;
}
void pilotfile::update_stats_backout(scoring_struct *stats, bool training)
{
	int i, j;
	uint idx;
	size_t list_size;
	index_list_t ilist;
	scoring_special_t *p_stats = NULL;

	if (Game_mode & GM_MULTIPLAYER) {
		p_stats = &multi_stats;
	} else {
		p_stats = &all_time_stats;
	}

	// medals
	if (stats->m_medal_earned >= 0) {
		list_size = p_stats->medals_earned.size();

		j = -1;

		for (idx = 0; idx < list_size; idx++) {
			if ( p_stats->medals_earned[idx].name.compare(Medals[stats->m_medal_earned].name) == 0 ) {
				j = idx;
				break;
			}
		}

		if (j >= 0) {
			p_stats->medals_earned[j].val = MAX(0,p_stats->medals_earned[j].val--);
		} else {
			Assertion(true, "Medal '%s' not found, should have been added by pilotfile::update_stats.", Medals[stats->m_medal_earned].name);
		}
	}

	// only medals can be awarded in training missions
	if (training) {
		return;
	}

	p_stats->score -= stats->m_score;

	p_stats->assists -= stats->m_assists;
	p_stats->kill_count -= stats->m_kill_count;
	p_stats->kill_count_ok -= stats->m_kill_count_ok;
	p_stats->bonehead_kills -= stats->m_bonehead_kills;

	p_stats->p_shots_fired -= stats->mp_shots_fired;
	p_stats->p_shots_hit -= stats->mp_shots_hit;
	p_stats->p_bonehead_hits -= stats->mp_bonehead_hits;

	p_stats->s_shots_fired -= stats->ms_shots_fired;
	p_stats->s_shots_hit -= stats->ms_shots_hit;
	p_stats->s_bonehead_hits -= stats->ms_bonehead_hits;

	p_stats->flight_time -= (unsigned int)f2i(Missiontime);
	p_stats->last_flown = p_stats->last_backup;
	p_stats->missions_flown--;

	if (stats->m_promotion_earned >= 0) {
		// deal with a multi-rank promotion mission
		for (i = 0; i < MAX_FREESPACE2_RANK; ++i) {
			if (p_stats->score <= Ranks[i].points) {
				p_stats->rank = i-1;
				break;
			}
		}
		Assertion (p_stats->rank >= 0, "Rank became negative.");
	}

	// badges
	if (stats->m_badge_earned.size()) {
		list_size = p_stats->medals_earned.size();
		for (size_t medal = 0; medal < stats->m_badge_earned.size(); medal++) {
			j = -1;

			for (idx = 0; idx < list_size; idx++) {
				if ( p_stats->medals_earned[idx].name.compare(Medals[stats->m_badge_earned[medal]].name) == 0 ) {
					j = idx;
					break;
				}
			}

			if (j >= 0) {
				p_stats->medals_earned[j].val = 0;
			} else {
				Assertion (false, "Badge '%s' not found, should have been added by pilotfile::update_stats.", Medals[stats->m_badge_earned[medal]].name);
			}
		}
	}

	// ship kills
	i = 0;
	for (auto it = Ship_info.cbegin(); it != Ship_info.cend(); i++, ++it) {
		if (stats->m_okKills[i] > 0) {
			list_size = p_stats->ship_kills.size();

			j = -1;

			for (idx = 0; idx < list_size; idx++) {
				if ( p_stats->ship_kills[idx].name.compare(it->name) == 0 ) {
					j = idx;
					break;
				}
			}

			if (j >= 0) {
				p_stats->ship_kills[j].val -= stats->m_okKills[i];
			} else {
				Assertion(false, "Ship kills of '%s' not found, should have been added by pilotfile::update_stats.", Ship_info[i].name);
			}
		}
	}
}