int
main(int ac, char **av)
{
   char buf[1024];
   char *str;

   openlog("sshlockout", LOG_PID|LOG_CONS, LOG_AUTH);
   syslog(LOG_ERR, "sshlockout starting up");
   freopen("/dev/null", "w", stdout);
   freopen("/dev/null", "w", stderr);

   while (fgets(buf, sizeof(buf), stdin) != NULL) {
       /* if this is not sshd related, continue on without processing */
       if (strstr(buf, "sshd") == NULL)
           continue;

       check_for_string("Failed password for root from", buf);
       check_for_string("Failed password for admin from", buf);
       check_for_string("Failed password for invalid user", buf);
       check_for_string("Illegal user", buf);
       check_for_string("authentication error for", buf);

   }
   syslog(LOG_ERR, "sshlockout exiting");
   return(0);
}
void parse_rank_tbl()
{
	atexit(scoreing_close);
	char buf[MULTITEXT_LENGTH];
	int rval, idx, persona;

	if ((rval = setjmp(parse_abort)) != 0) {
		mprintf(("TABLES: Unable to parse '%s'!  Error code = %i.\n", "rank.tbl", rval));
		return;
	} 

	read_file_text("rank.tbl", CF_TYPE_TABLES);
	reset_parse();

	// parse in all the rank names
	idx = 0;
	skip_to_string("[RANK NAMES]");
	ignore_white_space();
	while ( required_string_either("#End", "$Name:") ) {
		Assert ( idx < NUM_RANKS );
		required_string("$Name:");
		stuff_string( Ranks[idx].name, F_NAME, NAME_LENGTH );
		required_string("$Points:");
		stuff_int( &Ranks[idx].points );
		required_string("$Bitmap:");
		stuff_string( Ranks[idx].bitmap, F_NAME, MAX_FILENAME_LEN );
		required_string("$Promotion Voice Base:");
		stuff_string( Ranks[idx].promotion_voice_base, F_NAME, MAX_FILENAME_LEN );
		while (check_for_string("$Promotion Text:")) {
			required_string("$Promotion Text:");
			stuff_string(buf, F_MULTITEXT, sizeof(buf));
			drop_white_space(buf);
			compact_multitext_string(buf);
			persona = -1;
			if (optional_string("+Persona:")) {
				stuff_int(&persona);
				if (persona < 0) {
					Warning(LOCATION, "Debriefing text for %s rank is assigned to an invalid persona: %i (must be 0 or greater).\n", Ranks[idx].name, persona);
					continue;
				}
			}
			Ranks[idx].promotion_text[persona] = vm_strdup(buf);
		}
		if (Ranks[idx].promotion_text.find(-1) == Ranks[idx].promotion_text.end()) {
			Warning(LOCATION, "%s rank is missing default debriefing text.\n", Ranks[idx].name);
			Ranks[idx].promotion_text[-1] = "";
		}
		idx++;
	}

	required_string("#End");

	// be sure that all rank points are in order
#ifndef NDEBUG
	for ( idx = 0; idx < NUM_RANKS-1; idx++ ) {
		if ( Ranks[idx].points >= Ranks[idx+1].points )
			Int3();
	}
#endif
}
Beispiel #3
0
// parses help.tbl and populates help_overlaylist[]
void parse_helptbl()
{
	int overlay_id, currcount;
	char buf[HELP_MAX_STRING_LENGTH + 1];
	int i, rval;

	// open localization
	lcl_ext_open();

	if ((rval = setjmp(parse_abort)) != 0)
	{
		mprintf(("TABLES: Unable to parse '%s'!  Error code = %i.\n", HELP_OVERLAY_FILENAME, rval));
		lcl_ext_close();
		return;
	}

	read_file_text(HELP_OVERLAY_FILENAME, CF_TYPE_TABLES);

	// for each overlay...
	for (overlay_id = 0; overlay_id < MAX_HELP_OVERLAYS; overlay_id++)
	{

		reset_parse();
		skip_to_string(help_overlay_section_names[overlay_id]);

		// clear out counters in the overlay struct
		help_overlaylist[overlay_id].plinecount = 0;
		help_overlaylist[overlay_id].textcount = 0;
		help_overlaylist[overlay_id].rbracketcount = 0;
		help_overlaylist[overlay_id].lbracketcount = 0;

		// read in all elements for this overlay
		while (!(check_for_string("$end")))
		{

			if (optional_string("+pline"))
			{

				currcount = help_overlaylist[overlay_id].plinecount;
				int a, b;		// temp vars to read in int before cast to float;

				if (currcount < HELP_MAX_ITEM)
				{
					// read number of pline vertices
					stuff_int(&help_overlaylist[overlay_id].plinelist[GR_640][currcount].vtxcount);		// note that it is read into GR_640
					// help_overlaylist[overlay_id].plinelist[GR_1024][currcount].vtxcount = help_overlaylist[overlay_id].plinelist[GR_640][currcount].vtxcount;			// set equal to 1024 version vertex count to prevent bugs
					Assert(help_overlaylist[overlay_id].plinelist[GR_640][currcount].vtxcount <=
						HELP_MAX_PLINE_VERTICES);
					// get 640x480 vertex coordinates
					for (i = 0; i < help_overlaylist[overlay_id].plinelist[GR_640][currcount].vtxcount; i++)
					{
						stuff_int(&a);
						stuff_int(&b);
						help_overlaylist[overlay_id].plinelist[GR_640][currcount].vtx[i].xyz.x = (float)a;
						help_overlaylist[overlay_id].plinelist[GR_640][currcount].vtx[i].xyz.y = (float)b;
						help_overlaylist[overlay_id].plinelist[GR_640][currcount].vtx[i].xyz.z = 0.0f;
						help_overlaylist[overlay_id].plinelist[GR_640][currcount].pvtx[i] = &help_overlaylist[
							overlay_id].plinelist[GR_640][currcount].vtx[i];
					}
					// get 1024x768 vertex coordinates
					for (i = 0; i < help_overlaylist[overlay_id].plinelist[GR_640][currcount].vtxcount; i++)
					{
						stuff_int(&a);
						stuff_int(&b);
						help_overlaylist[overlay_id].plinelist[GR_1024][currcount].vtx[i].xyz.x = (float)a;
						help_overlaylist[overlay_id].plinelist[GR_1024][currcount].vtx[i].xyz.y = (float)b;
						help_overlaylist[overlay_id].plinelist[GR_1024][currcount].vtx[i].xyz.z = 0.0f;
						help_overlaylist[overlay_id].plinelist[GR_1024][currcount].pvtx[i] = &help_overlaylist[
							overlay_id].plinelist[GR_1024][currcount].vtx[i];
					}
				}

				//mprintf(("Found pline - start location (%f,%f), end location (%f,%f)\n", help_overlaylist[overlay_id].plinelist[GR_640][currcount].vtx[0].xyz.x, help_overlaylist[overlay_id].plinelist[GR_640][currcount].vtx[0].xyz.y, help_overlaylist[overlay_id].plinelist[GR_640][currcount].vtx[2].xyz.x, help_overlaylist[overlay_id].plinelist[GR_640][currcount].vtx[2].xyz.y));
				help_overlaylist[overlay_id].plinecount++;

			}
			else if (optional_string("+text"))
			{

				currcount = help_overlaylist[overlay_id].textcount;

				if (currcount < HELP_MAX_ITEM)
				{
					// get 640x480 coordinates
					stuff_int(&(help_overlaylist[overlay_id].textlist[GR_640][currcount].x_coord));
					stuff_int(&(help_overlaylist[overlay_id].textlist[GR_640][currcount].y_coord));
					// get 1024x768 coordinates
					stuff_int(&(help_overlaylist[overlay_id].textlist[GR_1024][currcount].x_coord));
					stuff_int(&(help_overlaylist[overlay_id].textlist[GR_1024][currcount].y_coord));

					// get string (always use the GR_640 one)
					stuff_string(buf, F_MESSAGE, sizeof(buf));
					help_overlaylist[overlay_id].textlist[GR_640][currcount].string = vm_strdup(buf);

					//mprintf(("Found text %d on overlay %d - location (%d,%d) @ 640x480 :: location (%d,%d) @ 1024x768\n", currcount, overlay_id, help_overlaylist[overlay_id].textlist[GR_640][currcount].x_coord, help_overlaylist[overlay_id].textlist[GR_640][currcount].y_coord, help_overlaylist[overlay_id].textlist[GR_1024][currcount].x_coord, help_overlaylist[overlay_id].textlist[GR_1024][currcount].x_coord));
					help_overlaylist[overlay_id].textcount++;
				}

			}
			else if (optional_string("+right_bracket"))
			{

				currcount = help_overlaylist[overlay_id].rbracketcount;

				if (currcount < HELP_MAX_ITEM)
				{
					// get 640x480 coordinates
					stuff_int(&(help_overlaylist[overlay_id].rbracketlist[GR_640][currcount].x_coord));
					stuff_int(&(help_overlaylist[overlay_id].rbracketlist[GR_640][currcount].y_coord));
					// get 1024x768 coordinates
					stuff_int(&(help_overlaylist[overlay_id].rbracketlist[GR_1024][currcount].x_coord));
					stuff_int(&(help_overlaylist[overlay_id].rbracketlist[GR_1024][currcount].y_coord));

					//mprintf(("Found rbracket %d on overlay %d - location (%d,%d) @ 640x480 :: location (%d,%d) @ 1024x768\n", currcount, overlay_id, help_overlaylist[overlay_id].rbracketlist[GR_640][currcount].x_coord, help_overlaylist[overlay_id].rbracketlist[GR_640][currcount].y_coord, help_overlaylist[overlay_id].rbracketlist[GR_1024][currcount].x_coord, help_overlaylist[overlay_id].rbracketlist[GR_1024][currcount].y_coord));
					help_overlaylist[overlay_id].rbracketcount++;
				}

			}
			else if (optional_string("+left_bracket"))
			{

				currcount = help_overlaylist[overlay_id].lbracketcount;

				if (currcount < HELP_MAX_ITEM)
				{
					// get 640x480 coordinates
					stuff_int(&(help_overlaylist[overlay_id].lbracketlist[GR_640][currcount].x_coord));
					stuff_int(&(help_overlaylist[overlay_id].lbracketlist[GR_640][currcount].y_coord));
					// get 1024x768 coordinates
					stuff_int(&(help_overlaylist[overlay_id].lbracketlist[GR_1024][currcount].x_coord));
					stuff_int(&(help_overlaylist[overlay_id].lbracketlist[GR_1024][currcount].y_coord));

					//mprintf(("Found lbracket %d on overlay %d - location (%d,%d) @ 640x480 :: location (%d,%d) @ 1024x768\n", currcount, overlay_id, help_overlaylist[overlay_id].lbracketlist[GR_640][currcount].x_coord, help_overlaylist[overlay_id].lbracketlist[GR_640][currcount].y_coord, help_overlaylist[overlay_id].lbracketlist[GR_1024][currcount].x_coord, help_overlaylist[overlay_id].lbracketlist[GR_1024][currcount].y_coord));
					help_overlaylist[overlay_id].lbracketcount++;
				}

			}
			else
			{
				// help.tbl is corrupt
				Assert(0);

			}		// end if

		}		// end while
	}		// end for

	// close localization
	lcl_ext_close();
}
void parse_everything_else(const char *filename)
{
	Assertion(filename != NULL, "parse_everything_else() called on NULL; get a coder!\n");
	read_file_text(filename, CF_TYPE_TABLES);

	int err_code;
	if ((err_code = setjmp(parse_abort)) != 0) {
		mprintf(("TABLES: Unable to parse '%s'!  Error code = %d.\n", filename, err_code));
		return;
	}

	reset_parse();

	int rgba[4] = {0,0,0,0};

	// reusable temp vars
	int i, j;
	SCP_string temp;

	if (optional_string("#Start Colors")) {
		// Skip this section; we already parsed it in every file.
		skip_to_string("#End", NULL);
	}

	//Team coloring
	if (optional_string("#Team Colors")) {

		while (required_string_either("#End", "$Team Name:")) {
			required_string("$Team Name:"); // required to move the parse pointer forward
			team_color temp_color;

			char temp2[NAME_LENGTH];
			stuff_string(temp2, F_NAME, NAME_LENGTH);
			temp = temp2;

			if (!stricmp(temp2, "none")) {
				Warning(LOCATION, "Team color in '%s' defined with a name of '%s'; this won't be usable due to 'None' being used for a lack of a team color by the engine.\n", filename, temp2);
			}

			if (required_string("$Team Stripe Color:")) {
				int rgb[3];
				stuff_int_list(rgb, 3, RAW_INTEGER_TYPE);
				for (i = 0; i < 3; i++) {
					CLAMP(rgb[i], 0, 255);
				}
				
				temp_color.stripe.r = rgb[0] / 255.0f;
				temp_color.stripe.g = rgb[1] / 255.0f;
				temp_color.stripe.b = rgb[2] / 255.0f;
			}

			if (required_string("$Team Base Color:")) {
				int rgb[3];
				stuff_int_list(rgb, 3, RAW_INTEGER_TYPE);
				for (i = 0; i < 3; i++) {
					CLAMP(rgb[i], 0, 255);
				}

				temp_color.base.r = rgb[0] / 255.0f;
				temp_color.base.g = rgb[1] / 255.0f;
				temp_color.base.b = rgb[2] / 255.0f;
			}

			if (Team_Colors.find(temp) == Team_Colors.end()) {	// Only push to the vector if the team isn't already defined.
				Team_Names.push_back(temp);
			}
			Team_Colors[temp] = temp_color;
		}
		required_string("#End");
	}

	// Previously-hardcoded interface colors
	if (optional_string("#Interface Colors")) {
		char *color_names[INTERFACE_COLORS] = {
			"$Text Normal:",
			"$Text Subselected:",
			"$Text Selected:",
			"$Text Error:",
			"$Text Error Highlighted:",
			"$Text Active:",
			"$Text Active Highlighted:",
			"$Text Heading:",
			"$More Indicator:",
			"$Bright More Indicator:",
			"$Bright:",
			"$Normal:",
		};

		// now for each color, check if its corresponding string is there
		for (i = 0; i < INTERFACE_COLORS; i++) {
			if (optional_string(color_names[i])) {
				// if so, get its rgba values and initialise it using them
				mprintf(("'%s' has been redefined.\n", color_names[i]));
				if ( check_for_string("(") ) {
					// If we have a list of integers, use them.
					stuff_int_list(rgba, 4, RAW_INTEGER_TYPE);
					for (j = 0; j < 4; j++) {
						if (rgba[j] < 0)
						{
							Warning(LOCATION, "RGBA value for '%s' in %s too low (%d), capping to 0.\n", color_names[i], filename, rgba[j]);
							rgba[j] = 0;
						}
						else if (rgba[j] > 255)
						{
							Warning(LOCATION, "RGBA value for '%s' in %s too high (%d), capping to 255.\n", color_names[i], filename, rgba[j]);
							rgba[j] = 255;
						}
					}
					gr_init_alphacolor(interface_colors[i], rgba[0], rgba[1], rgba[2], rgba[3]);
				//} else if (check_for_string("#")) {
				//	stuff_hex_list(rgba, 4);
				//	gr_init_alphacolor(interface_colors[i], rgba[0], rgba[1], rgba[2], rgba[3]);
				} else {
					// We have a string; it should be the name of a color to use.
					stuff_string(temp, F_NAME);
					for (j = 0; j < TOTAL_COLORS; j++) {
						if ( !temp.compare(COLOR_NAMES[j]) ) {
							break;
						}
					}
					if ( j == TOTAL_COLORS ) {
						Warning(LOCATION, "Unknown color '%s' in %s, for definition of '%s'; using default ('%s').\n", temp.c_str(), filename, color_names[i], COLOR_NAMES[interface_defaults[i]]);
					} else {
						Assertion(j >= 0 && j < TOTAL_COLORS, "Attempting to copy nonexistant color (%d out of 0-%d)!\n", j, TOTAL_COLORS-1);
						memcpy(interface_colors[i], COLOR_LIST[j], sizeof(color));
					}
				}
			}
		}
		required_string("#End");
	}

	// Text color tags; for briefings, command briefings, debriefings, and the fiction viewer
	if (optional_string("#Color Tags")) {
		while (required_string_either("$Tag:", "#End") < 1) {
			required_string("$Tag:");
			color temp_color;
			char tag;

			stuff_string(temp, F_RAW);
			if (temp[0] == '$') {
				if (temp[1] == '\0') {
					Error(LOCATION, "%s - found a '$Tag:' entry with a solitary '$'.\n", filename);
				}
				tag = temp[1];
				if (temp[2] != '\0') {
					Warning(LOCATION, "%s - tag '$%c' has extra text in its definition.\n", filename, tag);
				}
			} else if (temp[0] == '\0') {
				Error(LOCATION, "%s - found a '$Tag:' entry with no tag.\n", filename);
			} else {
				tag = temp[0];
				if (temp[1] != '\0') {
					Warning(LOCATION, "%s - tag '$%c' has extra text in its definition.\n", filename, tag);
				}
			}

			if (Tagged_Colors.find(tag) == Tagged_Colors.end()) {	// Only push the tag to our list of tags if it's actually new, not just a redefinition.
				Color_Tags.push_back(tag);
			}

			switch(required_string_one_of(4, "+Color:", "+Friendly", "+Hostile", "+Neutral")) {
			case 0:	// +Color
				required_string("+Color:");

				rgba[0] = rgba[1] = rgba[2] = 0;
				rgba[3] = 255;	// Odds are pretty high you want it to have full alpha...

				if ( check_for_string("(") ) {
					stuff_int_list(rgba, 4, RAW_INTEGER_TYPE);
					for (j = 0; j < 4; j++) {
						if (rgba[j] < 0)
						{
							Warning(LOCATION, "RGBA value for '$%c' in %s too low (%d), capping to 0.\n", tag, filename, rgba[j]);
							rgba[j] = 0;
						}
						else if (rgba[j] > 255)
						{
							Warning(LOCATION, "RGBA value for '$%c' in %s too high (%d), capping to 255.\n", tag, filename, rgba[j]);
							rgba[j] = 255;
						}
					}
					gr_init_alphacolor(&temp_color, rgba[0], rgba[1], rgba[2], rgba[3]);
					Custom_Colors[tag] = temp_color;
					Tagged_Colors[tag] = &Custom_Colors[tag];
				//} else if ( check_for_string ("#") ) {
				//	stuff_hex_list(rgba, 4);
				//	gr_init_alphacolor(&temp_color, rgba[0], rgba[1], rgba[2], rgba[3]);
				//	Custom_Colors[tag] = temp_color;
				//	Tagged_Colors[tag] = &Custom_Colors[tag];
				} else {
					// We have a string; it should be the name of a color to use.
					stuff_string(temp, F_NAME);
					for (j = 0; j < TOTAL_COLORS; j++) {
						if ( !temp.compare(COLOR_NAMES[j]) ) {
							break;
						}
					}
					if ( j == TOTAL_COLORS ) {
						Error(LOCATION, "Unknown color '%s' in %s, for definition of tag '$%c'.\n", temp.c_str(), filename, tag);
					}
					Tagged_Colors[tag] = COLOR_LIST[j];
				}
				break;
			case 1:	// +Friendly
				required_string("+Friendly");
				Tagged_Colors[tag] = &Brief_color_green;
				break;
			case 2:	// +Hostile
				required_string("+Hostile");
				Tagged_Colors[tag] = &Brief_color_red;
				break;
			case 3:	// +Neutral
				required_string("+Neutral");
				Tagged_Colors[tag] = &Brief_color_legacy_neutral;
				break;
			case -1:
				// -noparseerrors is set
				if (Tagged_Colors.find(tag) == Tagged_Colors.end()) {	// It was a new color, but since we haven't actually defined it...
					Color_Tags.pop_back();
				}
				break;
			default:
				Assertion(false, "MageKing17 made a coding error somewhere, and you should laugh at him (and report this error).\n");
				break;
			}
		}

		required_string("#End");
	}
	Assertion(Color_Tags.size() == Tagged_Colors.size(), "Color_Tags and Tagged_Colors size mismatch; get a coder!\n");

	if (optional_string("#Default Text Colors")) {

		char* color_names[MAX_DEFAULT_TEXT_COLORS] = {
			"$Fiction Viewer:",
			"$Command Briefing:",
			"$Briefing:",
			"$Redalert Briefing:",
			"$Debriefing:",
			"$Recommendation:",
			"$Loop Briefing:",
		};

		char *color_value[MAX_DEFAULT_TEXT_COLORS] = {
			&default_fiction_viewer_color,
			&default_command_briefing_color,
			&default_briefing_color,
			&default_redalert_briefing_color,
			&default_debriefing_color,
			&default_recommendation_color,
			&default_loop_briefing_color,
		};

		for (i = 0; i < MAX_DEFAULT_TEXT_COLORS; i++) {
			if ( optional_string(color_names[i]) ) {
				stuff_string(temp, F_RAW);
				if (temp[0] == '$') {
					if (temp[1] == '\0') {
						Error(LOCATION, "%s - default text color '%s' entry with a solitary '$'.\n", filename, color_names[i]);
					}
					*color_value[i] = temp[1];
					if (temp[2] != '\0') {
						Warning(LOCATION, "%s - default text color '%s' has extra text after the tag '$%c'.\n", filename, color_names[i], *color_value[i]);
					}
				} else if (temp[0] == '\0') {
					Error(LOCATION, "%s - default text color '%s' entry with no tag.\n", filename, color_names[i]);
				} else {
					*color_value[i] = temp[0];
					if (temp[1] != '\0') {
						Warning(LOCATION, "%s - default text color '%s' has extra text after the tag '$%c'.\n", filename, color_names[i], *color_value[i]);
					}
				}
				if (Tagged_Colors.find(*color_value[i]) == Tagged_Colors.end()) {
					// Just mprintf() this information instead of complaining with a Warning(); the tag might be defined in a later-loading TBM, and if it isn't, nothing too terrible will happen.
					mprintf(("%s - default text color '%s' set to non-existant tag '$%c'.\n", filename, color_names[i], *color_value[i]));
				}
			}
		}
		required_string("#End");
	}
}
void parse_medal_tbl()
{
	int i;

	try
	{
		read_file_text("medals.tbl", CF_TYPE_TABLES);
		reset_parse();

		required_string("#Medals");

		// special background information
		if (optional_string("+Background Bitmap:")) {
			stuff_string(Medals_background_filename, F_NAME, NAME_LENGTH);
		}
		else {
			strcpy_s(Medals_background_filename, Default_medals_background_filename);
		}

		// special mask information
		if (optional_string("+Mask Bitmap:")) {
			stuff_string(Medals_mask_filename, F_NAME, NAME_LENGTH);
		}
		else {
			strcpy_s(Medals_mask_filename, Default_medals_mask_filename);
		}

		// configurable hotspot for the exit button
		if (optional_string("+Exit Button Hotspot Index:")) {
			stuff_int(&Exit_button_hotspot_override);
		}

		// special positioning for player callsign
		if (optional_string("+Callsign Position 640:")) {
			stuff_int(&Medals_callsign_coords[GR_640].x);
			stuff_int(&Medals_callsign_coords[GR_640].y);
		}
		else {
			Medals_callsign_coords[GR_640].x = Default_medals_callsign_coords[GR_640][0];
			Medals_callsign_coords[GR_640].y = Default_medals_callsign_coords[GR_640][1];
		}
		if (optional_string("+Callsign Position 1024:")) {
			stuff_int(&Medals_callsign_coords[GR_1024].x);
			stuff_int(&Medals_callsign_coords[GR_1024].y);
		}
		else {
			Medals_callsign_coords[GR_1024].x = Default_medals_callsign_coords[GR_1024][0];
			Medals_callsign_coords[GR_1024].y = Default_medals_callsign_coords[GR_1024][1];
		}

		// special positioning for medal label
		if (optional_string("+Label Position 640:")) {
			stuff_int(&Medals_label_coords[GR_640].x);
			stuff_int(&Medals_label_coords[GR_640].y);
			stuff_int(&Medals_label_coords[GR_640].w);
		}
		else {
			Medals_label_coords[GR_640].x = Default_medals_label_coords[GR_640][0];
			Medals_label_coords[GR_640].y = Default_medals_label_coords[GR_640][1];
			Medals_label_coords[GR_640].w = Default_medals_label_coords[GR_640][2];
		}
		if (optional_string("+Label Position 1024:")) {
			stuff_int(&Medals_label_coords[GR_1024].x);
			stuff_int(&Medals_label_coords[GR_1024].y);
			stuff_int(&Medals_label_coords[GR_1024].w);
		}
		else {
			Medals_label_coords[GR_1024].x = Default_medals_label_coords[GR_1024][0];
			Medals_label_coords[GR_1024].y = Default_medals_label_coords[GR_1024][1];
			Medals_label_coords[GR_1024].w = Default_medals_label_coords[GR_1024][2];
		}

		// parse in all the medal names
		Num_medals = 0;
		while (required_string_either("#End", "$Name:"))
		{
			medal_stuff temp_medal;
			medal_display_info temp_display;

			required_string("$Name:");
			stuff_string(temp_medal.name, F_NAME, NAME_LENGTH);

			// is this rank?  if so, save it
			if (!stricmp(temp_medal.name, "Rank"))
				Rank_medal_index = Num_medals;

			required_string("$Bitmap:");
			stuff_string(temp_medal.bitmap, F_NAME, MAX_FILENAME_LEN);

			if (optional_string("+Position 640:")) {
				stuff_int(&temp_display.coords[GR_640].x);
				stuff_int(&temp_display.coords[GR_640].y);
			}
			else if (Num_medals < NUM_MEDALS_FS2) {
				temp_display.coords[GR_640].x = Default_medal_coords[GR_640][Num_medals][0];
				temp_display.coords[GR_640].y = Default_medal_coords[GR_640][Num_medals][1];
			}
			else {
				Warning(LOCATION, "No default GR_640 position for medal '%s'!", temp_medal.name);
				temp_display.coords[GR_640].x = 0;
				temp_display.coords[GR_640].y = 0;
			}
			if (optional_string("+Position 1024:")) {
				stuff_int(&temp_display.coords[GR_1024].x);
				stuff_int(&temp_display.coords[GR_1024].y);
			}
			else if (Num_medals < NUM_MEDALS_FS2) {
				temp_display.coords[GR_1024].x = Default_medal_coords[GR_1024][Num_medals][0];
				temp_display.coords[GR_1024].y = Default_medal_coords[GR_1024][Num_medals][1];
			}
			else {
				Warning(LOCATION, "No default GR_1024 position for medal '%s'!", temp_medal.name);
				temp_display.coords[GR_1024].x = 0;
				temp_display.coords[GR_1024].y = 0;
			}

			if (optional_string("+Debriefing Bitmap:")) {
				stuff_string(temp_medal.debrief_bitmap, F_NAME, MAX_FILENAME_LEN);
			}
			else if (Num_medals < NUM_MEDALS_FS2) {
				strcpy_s(temp_medal.debrief_bitmap, Default_debriefing_bitmaps[Num_medals]);
			}
			else {
				Warning(LOCATION, "No default debriefing bitmap for medal '%s'!", temp_medal.name);
				strcpy_s(temp_medal.debrief_bitmap, "");
			}

			required_string("$Num mods:");
			stuff_int(&temp_medal.num_versions);

			// this is dumb
			temp_medal.version_starts_at_1 = (Num_medals == Rank_medal_index);
			if (optional_string("+Version starts at 1:")) {
				stuff_boolean(&temp_medal.version_starts_at_1);
			}

			if (optional_string("+Available From Start:")) {
				stuff_boolean(&temp_medal.available_from_start);
			}

			// some medals are based on kill counts.  When string +Num Kills: is present, we know that
			// this medal is a badge and should be treated specially
			if (optional_string("+Num Kills:")) {
				char buf[MULTITEXT_LENGTH];
				int persona;
				stuff_int(&temp_medal.kills_needed);

				if (optional_string("$Wavefile 1:"))
					stuff_string(temp_medal.voice_base, F_NAME, MAX_FILENAME_LEN);

				if (optional_string("$Wavefile 2:"))
					stuff_string(temp_medal.voice_base, F_NAME, MAX_FILENAME_LEN);

				if (optional_string("$Wavefile Base:"))
					stuff_string(temp_medal.voice_base, F_NAME, MAX_FILENAME_LEN);

				while (check_for_string("$Promotion Text:")) {
					required_string("$Promotion Text:");
					stuff_string(buf, F_MULTITEXT, sizeof(buf));
					persona = -1;
					if (optional_string("+Persona:")) {
						stuff_int(&persona);
						if (persona < 0) {
							Warning(LOCATION, "Debriefing text for %s is assigned to an invalid persona: %i (must be 0 or greater).\n", temp_medal.name, persona);
							continue;
						}
					}
					temp_medal.promotion_text[persona] = SCP_string(buf);
				}
				if (temp_medal.promotion_text.find(-1) == temp_medal.promotion_text.end()) {
					Warning(LOCATION, "%s medal is missing default debriefing text.\n", temp_medal.name);
					temp_medal.promotion_text[-1] = "";
				}
			}

			Medals.push_back(temp_medal);
			Medal_display_info.push_back(temp_display);
			Num_medals++;
		}

		required_string("#End");

		// be sure that we know where the rank is
		if (Rank_medal_index < 0)
		{
			Warning(LOCATION, "Could not find the 'Rank' medal!");
			Rank_medal_index = 0;
		}

		// be sure that the badges kill numbers show up in order
		//WMC - I don't think this is needed anymore due to my changes to post-mission functions
		//but I'm keeping it here to be sure.
		int prev_badge_kills = 0;
		for (i = 0; i < Num_medals; i++)
		{
			if (Medals[i].kills_needed < prev_badge_kills && Medals[i].kills_needed != 0)
				Error(LOCATION, "Badges must appear sorted by lowest kill # first in medals.tbl\nFind Allender for most information.");

			if (Medals[i].kills_needed > 0)
				prev_badge_kills = Medals[i].kills_needed;
		}
	}
	catch (const parse::ParseException& e)
	{
		mprintf(("TABLES: Unable to parse '%s'!  Error message = %s.\n", "medals.tbl", e.what()));
		return;
	}
}
void parse_ai_profiles_tbl(const char *filename)
{
	int i;
	char profile_name[NAME_LENGTH];
	ai_profile_t dummy_profile;
	char *saved_Mp = NULL;
	char buf[NAME_LENGTH];

	try
	{
		if (filename == NULL)
			read_file_text_from_default(defaults_get_file("ai_profiles.tbl"));
		else
			read_file_text(filename, CF_TYPE_TABLES);

		reset_parse();


		// start parsing
		required_string("#AI Profiles");

		// new default?
		if (optional_string("$Default Profile:"))
			stuff_string(Default_profile_name, F_NAME, NAME_LENGTH);

		// begin reading data
		while (required_string_either("#End", "$Profile Name:"))
		{
			ai_profile_t *profile = &dummy_profile;
			ai_profile_t *previous_profile = NULL;
			bool no_create = false;

			// get the name
			required_string("$Profile Name:");
			stuff_string(profile_name, F_NAME, NAME_LENGTH);

			// see if it exists
			for (i = 0; i < Num_ai_profiles; i++)
			{
				if (!stricmp(Ai_profiles[i].profile_name, profile_name))
				{
					previous_profile = &Ai_profiles[i];
					break;
				}
			}

			// modular table stuff
			if (optional_string("+nocreate"))
			{
				no_create = true;

				// use the previous one if possible,
				// otherwise continue to use the dummy one
				if (previous_profile != NULL)
					profile = previous_profile;
			}
			else
			{
				// don't create multiple profiles with the same name
				if (previous_profile != NULL)
				{
					Warning(LOCATION, "An ai profile named '%s' already exists!  The new one will not be created.\n", profile_name);
				}
				else
				{
					// make sure we're under the limit
					if (Num_ai_profiles >= MAX_AI_PROFILES)
					{
						Warning(LOCATION, "Too many profiles in ai_profiles.tbl!  Max is %d.\n", MAX_AI_PROFILES - 1);	// -1 because one is built-in
						skip_to_string("#End", NULL);
						break;
					}

					profile = &Ai_profiles[Num_ai_profiles];
					Num_ai_profiles++;
				}
			}

			// initialize profile if we're not building from a previously parsed one
			if (!no_create)
			{
				// base profile, so zero it out
				if (profile == &Ai_profiles[0])
				{
                    profile->reset();
				}
				// brand new profile, so set it to the base defaults
				else
				{
                    *profile = Ai_profiles[0];
				}
			}

			// set the name
			strcpy_s(profile->profile_name, profile_name);


			// fill in any and all settings; they're all optional and can be in any order
			while (!check_for_string("$Profile Name:") && !check_for_string("#End"))
			{
				if (optional_string("$Player Afterburner Recharge Scale:"))
					parse_float_list(profile->afterburner_recharge_scale, NUM_SKILL_LEVELS);

				if (optional_string("$Max Beam Friendly Fire Damage:"))
					parse_float_list(profile->beam_friendly_damage_cap, NUM_SKILL_LEVELS);

				if (optional_string("$Player Countermeasure Life Scale:"))
					parse_float_list(profile->cmeasure_life_scale, NUM_SKILL_LEVELS);

				if (optional_string("$AI Countermeasure Firing Chance:"))
					parse_float_list(profile->cmeasure_fire_chance, NUM_SKILL_LEVELS);

				if (optional_string("$AI In Range Time:"))
					parse_float_list(profile->in_range_time, NUM_SKILL_LEVELS);

				if (optional_string("$AI Always Links Ammo Weapons:"))
					parse_float_list(profile->link_ammo_levels_always, NUM_SKILL_LEVELS);

				if (optional_string("$AI Maybe Links Ammo Weapons:"))
					parse_float_list(profile->link_ammo_levels_maybe, NUM_SKILL_LEVELS);

				if (optional_string("$Primary Ammo Burst Multiplier:"))
					parse_float_list(profile->primary_ammo_burst_mult, NUM_SKILL_LEVELS);

				if (optional_string("$AI Always Links Energy Weapons:"))
					parse_float_list(profile->link_energy_levels_always, NUM_SKILL_LEVELS);

				if (optional_string("$AI Maybe Links Energy Weapons:"))
					parse_float_list(profile->link_energy_levels_maybe, NUM_SKILL_LEVELS);

				if (optional_string("$Max Missles Locked on Player:") || optional_string("$Max Missiles Locked on Player:"))
					parse_int_list(profile->max_allowed_player_homers, NUM_SKILL_LEVELS);

				if (optional_string("$Max Player Attackers:"))
					parse_int_list(profile->max_attackers, NUM_SKILL_LEVELS);

				if (optional_string("$Max Incoming Asteroids:"))
					parse_int_list(profile->max_incoming_asteroids, NUM_SKILL_LEVELS);

				if (optional_string("$Player Damage Factor:") || optional_string("$AI Damage Reduction to Player Hull:"))
					parse_float_list(profile->player_damage_scale, NUM_SKILL_LEVELS);

				if (optional_string("$Player Subsys Damage Factor:") || optional_string("$AI Damage Reduction to Player Subsys:"))
					parse_float_list(profile->subsys_damage_scale, NUM_SKILL_LEVELS);

				// represented in fractions of F1_0
				if (optional_string("$Predict Position Delay:"))
				{
					int iLoop;
					float temp_list[NUM_SKILL_LEVELS];

					parse_float_list(temp_list, NUM_SKILL_LEVELS);

					for (iLoop = 0; iLoop < NUM_SKILL_LEVELS; iLoop++)
						profile->predict_position_delay[iLoop] = fl2f(temp_list[iLoop]);
				}

				if (optional_string("$AI Shield Manage Delay:") || optional_string("$AI Shield Manage Delays:"))
					parse_float_list(profile->shield_manage_delay, NUM_SKILL_LEVELS);

				if (optional_string("$Friendly AI Fire Delay Scale:"))
					parse_float_list(profile->ship_fire_delay_scale_friendly, NUM_SKILL_LEVELS);

				if (optional_string("$Hostile AI Fire Delay Scale:"))
					parse_float_list(profile->ship_fire_delay_scale_hostile, NUM_SKILL_LEVELS);

				if (optional_string("$Friendly AI Secondary Fire Delay Scale:"))
					parse_float_list(profile->ship_fire_secondary_delay_scale_friendly, NUM_SKILL_LEVELS);

				if (optional_string("$Hostile AI Secondary Fire Delay Scale:"))
					parse_float_list(profile->ship_fire_secondary_delay_scale_hostile, NUM_SKILL_LEVELS);

				if (optional_string("$AI Turn Time Scale:"))
					parse_float_list(profile->turn_time_scale, NUM_SKILL_LEVELS);

				if (optional_string("$Glide Attack Percent:")) {
					parse_float_list(profile->glide_attack_percent, NUM_SKILL_LEVELS);
					//Percent is nice for modders, but here in the code we want it betwwen 0 and 1.0
					//While we're at it, verify the range
					for (i = 0; i < NUM_SKILL_LEVELS; i++) {
						if (profile->glide_attack_percent[i] < 0.0f || profile->glide_attack_percent[i] > 100.0f) {
							Warning(LOCATION, "$Glide Attack Percent should be between 0 and 100.0 (read %f). Setting to 0.", profile->glide_attack_percent[i]);
							profile->glide_attack_percent[i] = 0.0f;
						}
						profile->glide_attack_percent[i] /= 100.0;
					}
				}

				if (optional_string("$Circle Strafe Percent:")) {
					parse_float_list(profile->circle_strafe_percent, NUM_SKILL_LEVELS);
					//Percent is nice for modders, but here in the code we want it betwwen 0 and 1.0
					//While we're at it, verify the range
					for (i = 0; i < NUM_SKILL_LEVELS; i++) {
						if (profile->circle_strafe_percent[i] < 0.0f || profile->circle_strafe_percent[i] > 100.0f) {
							Warning(LOCATION, "$Circle Strafe Percent should be between 0 and 100.0 (read %f). Setting to 0.", profile->circle_strafe_percent[i]);
							profile->circle_strafe_percent[i] = 0.0f;
						}
						profile->circle_strafe_percent[i] /= 100.0;
					}
				}

				if (optional_string("$Glide Strafe Percent:")) {
					parse_float_list(profile->glide_strafe_percent, NUM_SKILL_LEVELS);
					//Percent is nice for modders, but here in the code we want it betwwen 0 and 1.0
					//While we're at it, verify the range
					for (i = 0; i < NUM_SKILL_LEVELS; i++) {
						if (profile->glide_strafe_percent[i] < 0.0f || profile->glide_strafe_percent[i] > 100.0f) {
							Warning(LOCATION, "$Glide Strafe Percent should be between 0 and 100.0 (read %f). Setting to 0.", profile->glide_strafe_percent[i]);
							profile->glide_strafe_percent[i] = 0.0f;
						}
						profile->glide_strafe_percent[i] /= 100.0;
					}
				}

				if (optional_string("$Random Sidethrust Percent:")) {
					parse_float_list(profile->random_sidethrust_percent, NUM_SKILL_LEVELS);
					//Percent is nice for modders, but here in the code we want it betwwen 0 and 1.0
					//While we're at it, verify the range
					for (i = 0; i < NUM_SKILL_LEVELS; i++) {
						if (profile->random_sidethrust_percent[i] < 0.0f || profile->random_sidethrust_percent[i] > 100.0f) {
							Warning(LOCATION, "$Random Sidethrust Percent should be between 0 and 100.0 (read %f). Setting to 0.", profile->random_sidethrust_percent[i]);
							profile->random_sidethrust_percent[i] = 0.0f;
						}
						profile->random_sidethrust_percent[i] /= 100.0;
					}
				}

				if (optional_string("$Stalemate Time Threshold:"))
					parse_float_list(profile->stalemate_time_thresh, NUM_SKILL_LEVELS);

				if (optional_string("$Stalemate Distance Threshold:"))
					parse_float_list(profile->stalemate_dist_thresh, NUM_SKILL_LEVELS);

				if (optional_string("$Player Shield Recharge Scale:"))
					parse_float_list(profile->shield_energy_scale, NUM_SKILL_LEVELS);

				if (optional_string("$Player Weapon Recharge Scale:"))
					parse_float_list(profile->weapon_energy_scale, NUM_SKILL_LEVELS);

				if (optional_string("$Max Turret Target Ownage:"))
					parse_int_list(profile->max_turret_ownage_target, NUM_SKILL_LEVELS);

				if (optional_string("$Max Turret Player Ownage:"))
					parse_int_list(profile->max_turret_ownage_player, NUM_SKILL_LEVELS);

				if (optional_string("$Percentage Required For Kill Scale:"))
					parse_float_list(profile->kill_percentage_scale, NUM_SKILL_LEVELS);

				if (optional_string("$Percentage Required For Assist Scale:"))
					parse_float_list(profile->assist_percentage_scale, NUM_SKILL_LEVELS);

				if (optional_string("$Percentage Awarded For Capship Assist:"))
					parse_float_list(profile->assist_award_percentage_scale, NUM_SKILL_LEVELS);

				if (optional_string("$Repair Penalty:"))
					parse_int_list(profile->repair_penalty, NUM_SKILL_LEVELS);

				if (optional_string("$Delay Before Allowing Bombs to Be Shot Down:"))
					parse_float_list(profile->delay_bomb_arm_timer, NUM_SKILL_LEVELS);

				if (optional_string("$Chance AI Has to Fire Missiles at Player:"))
					parse_int_list(profile->chance_to_use_missiles_on_plr, NUM_SKILL_LEVELS);

				if (optional_string("$Max Aim Update Delay:"))
					parse_float_list(profile->max_aim_update_delay, NUM_SKILL_LEVELS);

				if (optional_string("$Turret Max Aim Update Delay:"))
					parse_float_list(profile->turret_max_aim_update_delay, NUM_SKILL_LEVELS);

				if (optional_string("$Player Autoaim FOV:"))
				{
					float fov_list[NUM_SKILL_LEVELS];
					parse_float_list(fov_list, NUM_SKILL_LEVELS);
					for (i = 0; i < NUM_SKILL_LEVELS; i++)
					{
						//Enforce range
						if (fov_list[i] < 0.0f || fov_list[i] >= 360.0f)
						{
							Warning(LOCATION, "$Player Autoaim FOV should be >= 0 and < 360.0 (read %f). Setting to 0.", fov_list[i]);
							fov_list[i] = 0.0f;
						}

						//Convert units
						profile->player_autoaim_fov[i] = fov_list[i] * PI / 180.0f;
					}
				}

				if (optional_string("$Detail Distance Multiplier:"))
					parse_float_list(profile->detail_distance_mult, MAX_DETAIL_LEVEL + 1);

				set_flag(profile, "$big ships can attack beam turrets on untargeted ships:", AI::Profile_Flags::Big_ships_can_attack_beam_turrets_on_untargeted_ships);

				set_flag(profile, "$smart primary weapon selection:", AI::Profile_Flags::Smart_primary_weapon_selection);

				set_flag(profile, "$smart secondary weapon selection:", AI::Profile_Flags::Smart_secondary_weapon_selection);

				set_flag(profile, "$smart shield management:", AI::Profile_Flags::Smart_shield_management);

				set_flag(profile, "$smart afterburner management:", AI::Profile_Flags::Smart_afterburner_management);

				set_flag(profile, "$free afterburner use:", AI::Profile_Flags::Free_afterburner_use);

				set_flag(profile, "$allow rapid secondary dumbfire:", AI::Profile_Flags::Allow_rapid_secondary_dumbfire);

				set_flag(profile, "$huge turret weapons ignore bombs:", AI::Profile_Flags::Huge_turret_weapons_ignore_bombs);

				set_flag(profile, "$don't insert random turret fire delay:", AI::Profile_Flags::Dont_insert_random_turret_fire_delay);

				set_flag(profile, "$hack improve non-homing swarm turret fire accuracy:", AI::Profile_Flags::Hack_improve_non_homing_swarm_turret_fire_accuracy);

				set_flag(profile, "$shockwaves damage small ship subsystems:", AI::Profile_Flags::Shockwaves_damage_small_ship_subsystems);

				set_flag(profile, "$navigation subsystem governs warpout capability:", AI::Profile_Flags::Navigation_subsys_governs_warp);

				set_flag(profile, "$ignore lower bound for minimum speed of docked ship:", AI::Profile_Flags::No_min_dock_speed_cap);

				set_flag(profile, "$disable linked fire penalty:", AI::Profile_Flags::Disable_linked_fire_penalty);

				set_flag(profile, "$disable weapon damage scaling:", AI::Profile_Flags::Disable_weapon_damage_scaling);

				set_flag(profile, "$use additive weapon velocity:", AI::Profile_Flags::Use_additive_weapon_velocity);

				set_flag(profile, "$use newtonian dampening:", AI::Profile_Flags::Use_newtonian_dampening);

				set_flag(profile, "$include beams for kills and assists:", AI::Profile_Flags::Include_beams_in_stat_calcs);

				set_flag(profile, "$score kills based on damage caused:", AI::Profile_Flags::Kill_scoring_scales_with_damage);

				set_flag(profile, "$score assists based on damage caused:", AI::Profile_Flags::Assist_scoring_scales_with_damage);

				set_flag(profile, "$allow event and goal scoring in multiplayer:", AI::Profile_Flags::Allow_multi_event_scoring);

				set_flag(profile, "$fix linked primary weapon decision bug:", AI::Profile_Flags::Fix_linked_primary_bug);

				set_flag(profile, "$prevent turrets targeting too distant bombs:", AI::Profile_Flags::Prevent_targeting_bombs_beyond_range);

				set_flag(profile, "$smart subsystem targeting for turrets:", AI::Profile_Flags::Smart_subsystem_targeting_for_turrets);

				set_flag(profile, "$fix heat seekers homing on stealth ships bug:", AI::Profile_Flags::Fix_heat_seeker_stealth_bug);

				set_flag(profile, "$multi allow empty primaries:", AI::Profile_Flags::Multi_allow_empty_primaries);

				set_flag(profile, "$multi allow empty secondaries:", AI::Profile_Flags::Multi_allow_empty_secondaries);

				set_flag(profile, "$allow turrets target weapons freely:", AI::Profile_Flags::Allow_turrets_target_weapons_freely);

				set_flag(profile, "$use only single fov for turrets:", AI::Profile_Flags::Use_only_single_fov_for_turrets);

				set_flag(profile, "$allow vertical dodge:", AI::Profile_Flags::Allow_vertical_dodge);

				set_flag(profile, "$force beam turrets to use normal fov:", AI::Profile_Flags::Force_beam_turret_fov);

				set_flag(profile, "$fix ai class bug:", AI::Profile_Flags::Fix_ai_class_bug);

				set_flag(profile, "$turrets ignore targets radius in range checks:", AI::Profile_Flags::Turrets_ignore_target_radius);

				set_flag(profile, "$no extra collision avoidance vs player:", AI::Profile_Flags::No_special_player_avoid);

				set_flag(profile, "$perform fewer checks for death screams:", AI::Profile_Flags::Perform_fewer_scream_checks);

				set_flag(profile, "$advanced turret fov edge checks:", AI::Profile_Flags::Advanced_turret_fov_edge_checks);

				set_flag(profile, "$require turrets to have target in fov:", AI::Profile_Flags::Require_turret_to_have_target_in_fov);

				set_flag(profile, "$all ships manage shields:", AI::Profile_Flags::All_ships_manage_shields);

				set_flag(profile, "$ai aims from ship center:", AI::Profile_Flags::Ai_aims_from_ship_center);

				set_flag(profile, "$allow primary link at mission start:", AI::Profile_Flags::Allow_primary_link_at_start);

				set_flag(profile, "$allow beams to damage bombs:", AI::Profile_Flags::Beams_damage_weapons);

				set_flag(profile, "$disable weapon damage scaling for player:", AI::Profile_Flags::Player_weapon_scale_fix);

				set_flag(profile, "$countermeasures affect aspect seekers:", AI::Profile_Flags::Aspect_lock_countermeasure);

				set_flag(profile, "$ai guards specific ship in wing:", AI::Profile_Flags::Ai_guards_specific_ship_in_wing);

				profile->ai_path_mode = AI_PATH_MODE_NORMAL;
				if (optional_string("$ai path mode:"))
				{
					stuff_string(buf, F_NAME, NAME_LENGTH);
					int j = ai_path_type_match(buf);
					if (j >= 0) {
						profile->ai_path_mode = j;
					}
					else {
						Warning(LOCATION, "Invalid ai path mode '%s' specified", buf);
					}
				}

				set_flag(profile, "$no warp camera:", AI::Profile_Flags::No_warp_camera);

				set_flag(profile, "$fix ai path order bug:", AI::Profile_Flags::Fix_ai_path_order_bug);

				set_flag(profile, "$strict turret-tagged-only targeting:", AI::Profile_Flags::Strict_turred_tagged_only_targeting);

				set_flag(profile, "$aspect bomb invulnerability fix:", AI::Profile_Flags::Aspect_invulnerability_fix);

				set_flag(profile, "$glide decay requires thrust:", AI::Profile_Flags::Glide_decay_requires_thrust);

				set_flag(profile, "$ai can slow down when attacking big ships:", AI::Profile_Flags::Ai_can_slow_down_attacking_big_ships);

				set_flag(profile, "$use actual primary range:", AI::Profile_Flags::Use_actual_primary_range);

				profile->bay_arrive_speed_mult = 1.0f;
				profile->bay_depart_speed_mult = 1.0f;
				if (optional_string("$bay arrive speed multiplier:")) {
					stuff_float(&profile->bay_arrive_speed_mult);
				}
				if (optional_string("$bay depart speed multiplier:")) {
					stuff_float(&profile->bay_depart_speed_mult);
				}

				// ----------

				// compatibility
				if (optional_string("$perform less checks for death screams:"))
				{
					mprintf(("Warning: \"$perform less checks for death screams\" flag is deprecated in favor of \"$perform fewer checks for death screams\"\n"));
					bool temp;
					stuff_boolean(&temp);
                    profile->flags.set(AI::Profile_Flags::Perform_fewer_scream_checks, temp);
				}
				if (optional_string("$allow primary link delay:"))
				{
					mprintf(("Warning: \"$allow primary link delay\" flag is deprecated in favor of \"$allow primary link at mission start\"\n"));
					bool temp;
					stuff_boolean(&temp);
                    profile->flags.set(AI::Profile_Flags::Allow_primary_link_at_start, !temp);
				}


				// if we've been through once already and are at the same place, force a move
				if (saved_Mp && (saved_Mp == Mp))
				{
					char tmp[60];
					memset(tmp, 0, 60);
					strncpy(tmp, Mp, 59);
					mprintf(("WARNING: Unrecognized parameter in ai_profiles: %s\n", tmp));

					Mp++;
				}

				// find next valid option
				if (!skip_to_start_of_string_either("$", "#"))
					break;
				saved_Mp = Mp;
			}
		}

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

	// add tbl/tbm to multiplayer validation list
	extern void fs2netd_add_table_validation(const char *tblname);
	fs2netd_add_table_validation(filename);
}
// Unified function for loading strings.tbl and tstrings.tbl (and their modular versions).
// The "external" parameter controls which format to load: true for tstrings.tbl, false for strings.tbl
void parse_stringstbl_common(const char *filename, const bool external)
{
	char chr, buf[4096];
	char language_tag[512];
	int z, index;
	char *p_offset = NULL;
	int offset_lo = 0, offset_hi = 0;

	read_file_text(filename, CF_TYPE_TABLES);
	reset_parse();

	// move down to the proper section		
	memset(language_tag, 0, sizeof(language_tag));
	strcpy_s(language_tag, "#");
	if (external && Lcl_current_lang == FS2_OPEN_DEFAULT_LANGUAGE){
		strcat_s(language_tag, "default");
	} else {
		strcat_s(language_tag, Lcl_languages[Lcl_current_lang].lang_name);
	}

	if ( skip_to_string(language_tag) != 1 ) {
		mprintf(("Current language not found in %s\n", filename));
		return;
	}

	// parse all the strings in this section of the table
	while ( !check_for_string("#") ) {
		int num_offsets_on_this_line = 0;

		stuff_int(&index);
		if (external) {
			ignore_white_space();
			get_string(buf, sizeof(buf));
			drop_trailing_white_space(buf);
		} else {
			stuff_string(buf, F_RAW, sizeof(buf));
		}

		if (external && (index < 0 || index >= LCL_MAX_STRINGS)) {
			error_display(0, "Invalid tstrings table index specified (%i). Please increment LCL_MAX_STRINGS in localize.cpp.", index);
			return;
		} else if (!external && (index < 0 || index >= XSTR_SIZE)) {
			Error(LOCATION, "Invalid strings table index specified (%i)", index);
		}
		
		if (!external) {
			size_t i = strlen(buf);

			while (i--) {
				if ( !isspace(buf[i]) )
					break;
			}

			// trim unnecessary end of string
			// Assert(buf[i] == '"');
			if (buf[i] != '"') {
				// probably an offset on this entry

				// drop down a null terminator (prolly unnecessary)
				buf[i+1] = 0;

				// back up over the potential offset
				while ( !is_white_space(buf[i]) )
					i--;

				// now back up over intervening spaces
				while ( is_white_space(buf[i]) )
					i--;

				num_offsets_on_this_line = 1;

				if (buf[i] != '"') {
					// could have a 2nd offset value (one for 640, one for 1024)
					// so back up again
					while ( !is_white_space(buf[i]) )
						i--;

					// now back up over intervening spaces
					while ( is_white_space(buf[i]) )
						i--;

					num_offsets_on_this_line = 2;
				}

				p_offset = &buf[i+1];			// get ptr to string section with offset in it

				if (buf[i] != '"')
					Error(LOCATION, "%s is corrupt", filename);		// now its an error
			}

			buf[i] = 0;

			// copy string into buf
			z = 0;
			for (i = 1; buf[i]; i++) {
				chr = buf[i];

				if (chr == '\\') {
					chr = buf[++i];

					if (chr == 'n')
						chr = '\n';
					else if (chr == 'r')
						chr = '\r';
				}

				buf[z++] = chr;
			}

			// null terminator on buf
			buf[z] = 0;
		}

		// write into Xstr_table (for strings.tbl) or Lcl_ext_str (for tstrings.tbl)
		if (Parsing_modular_table) {
			if ( external && (Lcl_ext_str[index] != NULL) ) {
				vm_free((void *) Lcl_ext_str[index]);
				Lcl_ext_str[index] = NULL;
			} else if ( !external && (Xstr_table[index].str != NULL) ) {
				vm_free((void *) Xstr_table[index].str);
				Xstr_table[index].str = NULL;
			}
		}

		if (external && (Lcl_ext_str[index] != NULL)) {
			Warning(LOCATION, "Tstrings table index %d used more than once", index);
		} else if (!external && (Xstr_table[index].str != NULL)) {
			Warning(LOCATION, "Strings table index %d used more than once", index);
		}

		if (external) {
			Lcl_ext_str[index] = vm_strdup(buf);
		} else {
			Xstr_table[index].str = vm_strdup(buf);
		}

		// the rest of this loop applies only to strings.tbl,
		// so we can move on to the next line if we're reading from tstrings.tbl
		if (external) {
			continue;
		}

		// read offset information, assume 0 if nonexistant
		if (p_offset != NULL) {
			if (sscanf(p_offset, "%d%d", &offset_lo, &offset_hi) < num_offsets_on_this_line) {
				// whatever is in the file ain't a proper offset
				Error(LOCATION, "%s is corrupt", filename);
			}
		}

		Xstr_table[index].offset_x = offset_lo;

		if (num_offsets_on_this_line == 2)
			Xstr_table[index].offset_x_hi = offset_hi;
		else
			Xstr_table[index].offset_x_hi = offset_lo;

		// clear out our vars
		p_offset = NULL;
		offset_lo = 0;
		offset_hi = 0;
	}
}
// initialize the xstr table
void lcl_xstr_init()
{
	char chr, buf[4096];
	char language_tag[512];	
	int i, z, index, rval;
	char *p_offset = NULL;
	int offset_lo = 0, offset_hi = 0;

	for (i=0; i<XSTR_SIZE; i++){
		Xstr_table[i].str = NULL;
	}

	if ((rval = setjmp(parse_abort)) != 0) {
		mprintf(("Error parsing 'strings.tbl'\nError code = %i.\n", rval));
	} else {
		// make sure localization is NOT running
		lcl_ext_close();

		read_file_text("strings.tbl");
		reset_parse();

		// move down to the proper section		
		memset(language_tag, 0, 512);
		strcpy(language_tag, "#");
		strcat(language_tag, Lcl_languages[Lcl_current_lang].lang_name);
		if(skip_to_string(language_tag) != 1){
			Error(LOCATION, NOX("Strings.tbl is corrupt"));
		}		

		// parse all the strings in this section of the table
		while (!check_for_string("#")) {
			int num_offsets_on_this_line = 0;

			stuff_int(&index);
			stuff_string(buf, F_NAME, NULL, 4096);
			i = strlen(buf);
			while (i--) {
				if (!isspace(buf[i])) {
					break;
				}
			}

			// trim unneccesary end of string
			if (i >= 0) {
				// Assert(buf[i] == '"');
				if (buf[i] != '"') {
					// probably an offset on this entry
					buf[i+1] = 0;						// drop down a null terminator (prolly unnecessary)
					while(!is_white_space(buf[i])) i--;	// back up over the potential offset
					while(is_white_space(buf[i])) i--;	// now back up over intervening spaces
					num_offsets_on_this_line = 1;
					if (buf[i] != '"') {
						// could have a 2nd offset value (one for 640, one for 1024)
						// so back up again
						while(!is_white_space(buf[i])) i--;	// back up over the 2nd offset
						while(is_white_space(buf[i])) i--;	// now back up over intervening spaces
						num_offsets_on_this_line = 2;
					}

					p_offset = &buf[i+1];			// get ptr to string section with offset in it
					if (buf[i] != '"') {
						Error(LOCATION, NOX("Strings.tbl is corrupt"));		// now its an error
					}
				}

				buf[i] = 0;
			}

			// copy string into buf
			z = 0;
			for (i=1; buf[i]; i++) {
				chr = buf[i];
				if (chr == '\\') {
					chr = buf[++i];
					if (chr == 'n') {
						chr = '\n';
					} else if (chr == 'r') {
						chr = '\r';
					}

				}

				buf[z++] = chr;
			}

			// null terminator on buf
			buf[z] = 0;

			// write into Xstr_table
			if (index >= 0 && index < XSTR_SIZE) {
				if (Xstr_table[index].str != NULL) {
					Warning(LOCATION, "Strings table index %d used more than once", index);
				}
				Xstr_table[index].str = strdup(buf);
			}

			// read offset information, assume 0 if nonexistant
			if (p_offset != NULL) {
				if (sscanf(p_offset, "%d%d", &offset_lo, &offset_hi) < num_offsets_on_this_line) {
					// whatever is in the file ain't a proper offset
					Error(LOCATION, NOX("Strings.tbl is corrupt"));
				}
			}
			Xstr_table[index].offset_x = offset_lo;
			if (num_offsets_on_this_line == 2) {
				Xstr_table[index].offset_x_hi = offset_hi;
			} else {
				Xstr_table[index].offset_x_hi = offset_lo;						
			}

			// clear out our vars
			p_offset = NULL;
			offset_lo = 0;
			offset_hi = 0;
			num_offsets_on_this_line = 0;
		}
	}

	Xstr_inited = 1;
}
void parse_sound_table(const char* filename)
{
	try
	{
		read_file_text(filename, CF_TYPE_TABLES);
		reset_parse();

		// Parse the gameplay sounds section
		if (optional_string("#Game Sounds Start"))
		{
			while (!check_for_string("#Game Sounds End"))
			{
				game_snd tempSound;
				if (gamesnd_parse_line(&tempSound, "$Name:", &Snds))
				{
					Snds.push_back(game_snd(tempSound));
				}
			}

			required_string("#Game Sounds End");
		}

		// Parse the interface sounds section
		if (optional_string("#Interface Sounds Start"))
		{
			while (!check_for_string("#Interface Sounds End"))
			{
				game_snd tempSound;
				if (gamesnd_parse_line(&tempSound, "$Name:", &Snds_iface))
				{
					Snds_iface.push_back(game_snd(tempSound));
					Snds_iface_handle.push_back(-1);
				}
			}

			required_string("#Interface Sounds End");
		}

		// parse flyby sound section
		if (optional_string("#Flyby Sounds Start"))
		{
			char species_name_tag[NAME_LENGTH + 2];
			int	sanity_check = 0;
			size_t i;

			while (!check_for_string("#Flyby Sounds End") && (sanity_check <= (int)Species_info.size()))
			{
				for (i = 0; i < Species_info.size(); i++)
				{
					species_info *species = &Species_info[i];

					sprintf(species_name_tag, "$%s:", species->species_name);

					if (check_for_string(species_name_tag))
					{
						gamesnd_parse_line(&species->snd_flyby_fighter, species_name_tag);
						gamesnd_parse_line(&species->snd_flyby_bomber, species_name_tag);
						sanity_check--;
					}
					else
					{
						sanity_check++;
					}
				}
			}

			required_string("#Flyby Sounds End");
		}

		if (optional_string("#Sound Environments Start"))
		{
			parse_sound_environments();
		}
	}
	catch (const parse::ParseException& e)
	{
		mprintf(("TABLES: Unable to parse '%s'!  Error message = %s.\n", filename, e.what()));
		return;
	}

	gamesnd_add_retail_default_enhanced_sound_data();
}
Beispiel #10
0
/**
 * Parse the table
 */
void iff_init()
{
	char traitor_name[NAME_LENGTH];
	char attack_names[MAX_IFFS][MAX_IFFS][NAME_LENGTH];
	struct {
		char iff_name[NAME_LENGTH];
		int color_index;
	} observed_color_table[MAX_IFFS][MAX_IFFS];

	int num_attack_names[MAX_IFFS];
	int num_observed_colors[MAX_IFFS];
	int i, j, k;
	int string_idx;

	try
	{
		// Goober5000 - if table doesn't exist, use the default table
		if (cf_exists_full("iff_defs.tbl", CF_TYPE_TABLES))
			read_file_text("iff_defs.tbl", CF_TYPE_TABLES);
		else
			read_file_text_from_array(defaults_get_file("iff_defs.tbl"));

		reset_parse();

		// parse the table --------------------------------------------------------

		required_string("#IFFs");

		// get the traitor
		required_string("$Traitor IFF:");
		stuff_string(traitor_name, F_NAME, NAME_LENGTH);

		int rgb[3];

		// check if alternate colours are wanted to be used for these
		// Marks various stuff... like asteroids
		if ((optional_string("$Selection Color:")) || (optional_string("$Selection Colour:")))
		{
			stuff_int_list(rgb, 3, RAW_INTEGER_TYPE);
			iff_init_color(rgb[0], rgb[1], rgb[2]);
		}
		else
			iff_init_color(0xff, 0xff, 0xff);

		// Marks the ship currently saying something
		if ((optional_string("$Message Color:")) || (optional_string("$Message Colour:")))
		{
			stuff_int_list(rgb, 3, RAW_INTEGER_TYPE);
			iff_init_color(rgb[0], rgb[1], rgb[2]);
		}
		else
			iff_init_color(0x7f, 0x7f, 0x7f);

		// Marks the tagged ships
		if ((optional_string("$Tagged Color:")) || (optional_string("$Tagged Colour:")))
		{
			stuff_int_list(rgb, 3, RAW_INTEGER_TYPE);
			iff_init_color(rgb[0], rgb[1], rgb[2]);
		}
		else
			iff_init_color(0xff, 0xff, 0x00);

		// init radar blips colour table
		int a_bright, a_dim;
		bool alternate_blip_color = false;
		for (i = 0; i < 5; i++)
		{
			for (j = 0; j < 2; j++)
			{
				for (k = 0; k < 3; k++)
				{
					radar_iff_color[i][j][k] = -1;
				}
			}
		}

		// if the bright/dim scaling is wanted to be changed
		if (optional_string("$Dimmed IFF brightness:"))
		{
			int dim_iff_brightness;
			stuff_int(&dim_iff_brightness);
			Assert(dim_iff_brightness >= 0 && dim_iff_brightness <= HUD_COLOR_ALPHA_MAX);
			*iff_color_brightness = dim_iff_brightness;
		}
		else
			*iff_color_brightness = 4;

		// alternate = use same method as with ship blips
		// retail = use 1/2 intensities
		if (optional_string("$Use Alternate Blip Coloring:") || optional_string("$Use Alternate Blip Colouring:"))
		{
			stuff_boolean(&alternate_blip_color);
		}

		// Parse blip colours, their order is hardcoded.
		if ((optional_string("$Missile Blip Color:")) || (optional_string("$Missile Blip Colour:")))
		{
			stuff_int_list(rgb, 3, RAW_INTEGER_TYPE);
			for (i = 0; i < 3; i++)
			{
				Assert(rgb[i] >= 0 && rgb[i] <= 255);
				radar_iff_color[0][1][i] = rgb[i];
				radar_iff_color[0][0][i] = rgb[i] / 2;
			}
		}

		if ((optional_string("$Navbuoy Blip Color:")) || (optional_string("$Navbuoy Blip Colour:")))
		{
			stuff_int_list(rgb, 3, RAW_INTEGER_TYPE);
			for (i = 0; i < 3; i++)
			{
				Assert(rgb[i] >= 0 && rgb[i] <= 255);
				radar_iff_color[1][1][i] = rgb[i];
				radar_iff_color[1][0][i] = rgb[i] / 2;
			}
		}

		if ((optional_string("$Warping Blip Color:")) || (optional_string("$Warping Blip Colour:")))
		{
			stuff_int_list(rgb, 3, RAW_INTEGER_TYPE);
			for (i = 0; i < 3; i++)
			{
				Assert(rgb[i] >= 0 && rgb[i] <= 255);
				radar_iff_color[2][1][i] = rgb[i];
				radar_iff_color[2][0][i] = rgb[i] / 2;
			}
		}

		if ((optional_string("$Node Blip Color:")) || (optional_string("$Node Blip Colour:")))
		{
			stuff_int_list(rgb, 3, RAW_INTEGER_TYPE);
			for (i = 0; i < 3; i++)
			{
				Assert(rgb[i] >= 0 && rgb[i] <= 255);
				radar_iff_color[3][1][i] = rgb[i];
				radar_iff_color[3][0][i] = rgb[i] / 2;
			}
		}

		if ((optional_string("$Tagged Blip Color:")) || (optional_string("$Tagged Blip Colour:")))
		{
			stuff_int_list(rgb, 3, RAW_INTEGER_TYPE);
			for (i = 0; i < 3; i++)
			{
				Assert(rgb[i] >= 0 && rgb[i] <= 255);
				radar_iff_color[4][1][i] = rgb[i];
				radar_iff_color[4][0][i] = rgb[i] / 2;
			}
		}

		if (alternate_blip_color == true)
		{
			a_bright = iff_get_alpha_value(true);
			a_dim = iff_get_alpha_value(false);
			for (i = 0; i < 5; i++)
			{
				if (radar_iff_color[i][0][0] >= 0)
				{
					for (j = 0; j < 3; j++)
					{
						radar_iff_color[i][0][j] = radar_iff_color[i][1][j];
					}

					radar_iff_color[i][1][3] = a_bright;
					radar_iff_color[i][0][3] = a_dim;
				}
			}
		}
		else
		{
			for (i = 0; i < 5; i++)
			{
				if (radar_iff_color[i][0][0] >= 0)
				{
					radar_iff_color[i][0][3] = 255;
					radar_iff_color[i][1][3] = 255;
				}
			}
		}

		if (optional_string("$Radar Target ID Flags:")) {
			parse_string_flag_list((int*)&radar_target_id_flags, rti_flags, Num_rti_flags);
			if (optional_string("+reset"))
				radar_target_id_flags = 0;
		}

		// begin reading data
		Num_iffs = 0;
		while (required_string_either("#End", "$IFF Name:"))
		{
			iff_info *iff;
			int cur_iff;

			// make sure we're under the limit
			if (Num_iffs >= MAX_IFFS)
			{
				Warning(LOCATION, "Too many iffs in iffs_defs.tbl!  Max is %d.\n", MAX_IFFS);
				skip_to_start_of_string("#End", NULL);
				break;
			}

			// add new IFF
			iff = &Iff_info[Num_iffs];
			cur_iff = Num_iffs;
			Num_iffs++;


			// get required IFF info ----------------------------------------------

			// get the iff name
			required_string("$IFF Name:");
			stuff_string(iff->iff_name, F_NAME, NAME_LENGTH);

			// get the iff color
			if (check_for_string("$Colour:"))
				required_string("$Colour:");
			else
				required_string("$Color:");
			stuff_int_list(rgb, 3, RAW_INTEGER_TYPE);
			iff->color_index = iff_init_color(rgb[0], rgb[1], rgb[2]);


			// get relationships between IFFs -------------------------------------

			// get the list of iffs attacked
			if (optional_string("$Attacks:"))
				num_attack_names[cur_iff] = stuff_string_list(attack_names[cur_iff], MAX_IFFS);
			else
				num_attack_names[cur_iff] = 0;

			// get the list of observed colors
			num_observed_colors[cur_iff] = 0;
			while (optional_string("+Sees"))
			{
				// get iff observed
				stuff_string_until(observed_color_table[cur_iff][num_observed_colors[cur_iff]].iff_name, "As:", NAME_LENGTH);
				required_string("As:");

				// get color observed
				stuff_int_list(rgb, 3, RAW_INTEGER_TYPE);
				observed_color_table[cur_iff][num_observed_colors[cur_iff]].color_index = iff_init_color(rgb[0], rgb[1], rgb[2]);

				// increment
				num_observed_colors[cur_iff]++;
			}


			// get flags ----------------------------------------------------------

			// get iff flags
			iff->flags = 0;
			if (optional_string("$Flags:"))
			{
				char flag_strings[MAX_IFF_FLAGS][NAME_LENGTH];

				int num_strings = stuff_string_list(flag_strings, MAX_IFF_FLAGS);
				for (string_idx = 0; string_idx < num_strings; string_idx++)
				{
					if (!stricmp(NOX("support allowed"), flag_strings[string_idx]))
						iff->flags |= IFFF_SUPPORT_ALLOWED;
					else if (!stricmp(NOX("exempt from all teams at war"), flag_strings[string_idx]))
						iff->flags |= IFFF_EXEMPT_FROM_ALL_TEAMS_AT_WAR;
					else if (!stricmp(NOX("orders hidden"), flag_strings[string_idx]))
						iff->flags |= IFFF_ORDERS_HIDDEN;
					else if (!stricmp(NOX("orders shown"), flag_strings[string_idx]))
						iff->flags |= IFFF_ORDERS_SHOWN;
					else if (!stricmp(NOX("wing name hidden"), flag_strings[string_idx]))
						iff->flags |= IFFF_WING_NAME_HIDDEN;
					else
						Warning(LOCATION, "Bogus string in iff flags: %s\n", flag_strings[string_idx]);
				}
			}

			// get default ship flags
			iff->default_parse_flags = 0;
			if (optional_string("$Default Ship Flags:"))
			{
				i = 0;
				j = 0;
				char flag_strings[MAX_PARSE_OBJECT_FLAGS][NAME_LENGTH];
				int num_strings = stuff_string_list(flag_strings, MAX_PARSE_OBJECT_FLAGS);
				for (i = 0; i < num_strings; i++)
				{
					for (j = 0; j < MAX_PARSE_OBJECT_FLAGS; j++)
					{
						if (!stricmp(flag_strings[i], Parse_object_flags[j]))
						{
							iff->default_parse_flags |= (1 << j);
							break;
						}
					}
				}

				if (j == MAX_PARSE_OBJECT_FLAGS)
					Warning(LOCATION, "Bogus string in iff default ship flags: %s\n", flag_strings[i]);
			}

			// again
			iff->default_parse_flags2 = 0;
			if (optional_string("$Default Ship Flags2:"))
			{
				i = 0;
				j = 0;
				char flag_strings[MAX_PARSE_OBJECT_FLAGS_2][NAME_LENGTH];
				int num_strings = stuff_string_list(flag_strings, MAX_PARSE_OBJECT_FLAGS_2);
				for (i = 0; i < num_strings; i++)
				{
					for (j = 0; j < MAX_PARSE_OBJECT_FLAGS_2; j++)
					{
						if (!stricmp(flag_strings[i], Parse_object_flags_2[j]))
						{
							iff->default_parse_flags2 |= (1 << j);
							break;
						}
					}
				}

				if (j == MAX_PARSE_OBJECT_FLAGS_2)
					Warning(LOCATION, "Bogus string in iff default ship flags2: %s\n", flag_strings[i]);
			}

			// this is cleared between each level but let's just set it here for thoroughness
			iff->ai_rearm_timestamp = timestamp(-1);
		}

		required_string("#End");


		// now resolve the relationships ------------------------------------------

		// first get the traitor
		Iff_traitor = iff_lookup(traitor_name);
		if (Iff_traitor < 0)
		{
			Iff_traitor = 0;
			Warning(LOCATION, "Traitor IFF %s not found in iff_defs.tbl!  Defaulting to %s.\n", traitor_name, Iff_info[Iff_traitor].iff_name);
		}

		// next get the attackees and colors
		for (int cur_iff = 0; cur_iff < Num_iffs; cur_iff++)
		{
			iff_info *iff = &Iff_info[cur_iff];

			// clear the iffs to be attacked
			iff->attackee_bitmask = 0;
			iff->attackee_bitmask_all_teams_at_war = 0;

			// clear the observed colors
			for (j = 0; j < MAX_IFFS; j++)
				iff->observed_color_index[j] = -1;

			// resolve the list names
			for (int list_index = 0; list_index < MAX_IFFS; list_index++)
			{
				// are we within the number of attackees listed?
				if (list_index < num_attack_names[cur_iff])
				{
					// find out who
					int target_iff = iff_lookup(attack_names[cur_iff][list_index]);

					// valid?
					if (target_iff >= 0)
						iff->attackee_bitmask |= iff_get_mask(target_iff);
					else
						Warning(LOCATION, "Attack target IFF %s not found for IFF %s in iff_defs.tbl!\n", attack_names[cur_iff][list_index], iff->iff_name);
				}

				// are we within the number of colors listed?
				if (list_index < num_observed_colors[cur_iff])
				{
					// find out who
					int target_iff = iff_lookup(observed_color_table[cur_iff][list_index].iff_name);

					// valid?
					if (target_iff >= 0)
						iff->observed_color_index[target_iff] = observed_color_table[cur_iff][list_index].color_index;
					else
						Warning(LOCATION, "Observed color IFF %s not found for IFF %s in iff_defs.tbl!\n", observed_color_table[cur_iff][list_index].iff_name, iff->iff_name);
				}
			}

			// resolve the all teams at war relationships
			if (iff->flags & IFFF_EXEMPT_FROM_ALL_TEAMS_AT_WAR)
			{
				// exempt, so use standard attacks
				iff->attackee_bitmask_all_teams_at_war = iff->attackee_bitmask;
			}
			else
			{
				// nonexempt, so build bitmask of all other nonexempt teams
				for (int other_iff = 0; other_iff < Num_iffs; other_iff++)
				{
					// skip myself (unless I attack myself normally)
					if ((other_iff == cur_iff) && !iff_x_attacks_y(cur_iff, cur_iff))
						continue;

					// skip anyone exempt
					if (Iff_info[other_iff].flags & IFFF_EXEMPT_FROM_ALL_TEAMS_AT_WAR)
						continue;

					// add everyone else
					iff->attackee_bitmask_all_teams_at_war |= iff_get_mask(other_iff);
				}
			}
		}

		// add tbl/tbm to multiplayer validation list
		extern void fs2netd_add_table_validation(const char *tblname);
		fs2netd_add_table_validation("iff_defs.tbl");
	}
	catch (const parse::ParseException& e)
	{
		mprintf(("TABLES: Unable to parse '%s'!  Error message = %s.\n", "iff_defs.tbl", e.what()));
		return;
	}
}
Beispiel #11
0
// -------------------------------------------------------------------------------------------------
// gamesnd_parse_soundstbl() will parse the sounds.tbl file, and load the specified sounds.
//
//
void gamesnd_parse_soundstbl()
{
	int rval;
	int num_game_sounds = 0;
	int num_iface_sounds = 0;
	int i;
	char cstrtemp[NAME_LENGTH+3];
	char* missing_species_names = NULL;
	ubyte* missing_species = NULL;
	int sanity_check = 0;

	gamesnd_init_sounds();

	// open localization
	lcl_ext_open();

	if ((rval = setjmp(parse_abort)) != 0)
	{
		mprintf(("TABLES: Unable to parse '%s'!  Error code = %i.\n", "sounds.tbl", rval));
		lcl_ext_close();
		return;
	}

	read_file_text("sounds.tbl", CF_TYPE_TABLES);
	reset_parse();

	// Parse the gameplay sounds section
	required_string("#Game Sounds Start");
	while (required_string_either("#Game Sounds End", "$Name:"))
	{
		Assert(num_game_sounds < Num_game_sounds);
		gamesnd_parse_line(&Snds[num_game_sounds], "$Name:");
		num_game_sounds++;
		gamesnd_add_sound_slot(GAME_SND, num_game_sounds);
	}
	required_string("#Game Sounds End");

	// Parse the interface sounds section
	required_string("#Interface Sounds Start");
	while (required_string_either("#Interface Sounds End", "$Name:"))
	{
		Assert(num_iface_sounds < Num_iface_sounds);
		gamesnd_parse_line(&Snds_iface[num_iface_sounds], "$Name:");
		num_iface_sounds++;
		gamesnd_add_sound_slot(IFACE_SND, num_iface_sounds);
	}
	required_string("#Interface Sounds End");

	// parse flyby sound section	
	required_string("#Flyby Sounds Start");

	missing_species_names = new char[Species_info.size() * (NAME_LENGTH + 2)];
	missing_species = new ubyte[Species_info.size()];

	memset(missing_species_names, 0, Species_info.size() * (NAME_LENGTH + 2));
	memset(missing_species, 1, Species_info.size());	// assume they are all missing

	while (!check_for_string("#Flyby Sounds End") && (sanity_check <= (int)Species_info.size()))
	{
		for (i = 0; i < (int)Species_info.size(); i++)
		{
			species_info* species = &Species_info[i];

			sprintf(cstrtemp, "$%s:", species->species_name);

			if (check_for_string(cstrtemp))
			{
				gamesnd_parse_line(&species->snd_flyby_fighter, cstrtemp);
				gamesnd_parse_line(&species->snd_flyby_bomber, cstrtemp);
				missing_species[i] = 0;
				sanity_check--;
			}
			else
			{
				sanity_check++;
			}
		}
	}

	// if we are missing any species then report it
	for (i = 0; i < (int)Species_info.size(); i++)
	{
		if (missing_species[i])
		{
			strcat(missing_species_names, Species_info[i].species_name);
			strcat(missing_species_names, "\n");
		}
	}

	if (strlen(missing_species_names))
	{
		Error(LOCATION, "The following species are missing flyby sounds in sounds.tbl:\n%s", missing_species_names);
	}

	delete[] missing_species_names;
	delete[] missing_species;

	required_string("#Flyby Sounds End");

	// close localization
	lcl_ext_close();
}
void parse_ai_profiles_tbl(const char *filename)
{
	int i;
	char profile_name[NAME_LENGTH];
	ai_profile_t dummy_profile;
	char *saved_Mp = NULL;
	char buf[NAME_LENGTH];

	try
	{
		if (filename == NULL)
			read_file_text_from_default(defaults_get_file("ai_profiles.tbl"));
		else
			read_file_text(filename, CF_TYPE_TABLES);

		reset_parse();


		// start parsing
		required_string("#AI Profiles");

		// new default?
		if (optional_string("$Default Profile:"))
			stuff_string(Default_profile_name, F_NAME, NAME_LENGTH);

		// begin reading data
		while (required_string_either("#End", "$Profile Name:"))
		{
			ai_profile_t *profile = &dummy_profile;
			ai_profile_t *previous_profile = NULL;
			bool no_create = false;

			// get the name
			required_string("$Profile Name:");
			stuff_string(profile_name, F_NAME, NAME_LENGTH);

			// see if it exists
			for (i = 0; i < Num_ai_profiles; i++)
			{
				if (!stricmp(Ai_profiles[i].profile_name, profile_name))
				{
					previous_profile = &Ai_profiles[i];
					break;
				}
			}

			// modular table stuff
			if (optional_string("+nocreate"))
			{
				no_create = true;

				// use the previous one if possible,
				// otherwise continue to use the dummy one
				if (previous_profile != NULL)
					profile = previous_profile;
			}
			else
			{
				// don't create multiple profiles with the same name
				if (previous_profile != NULL)
				{
					Warning(LOCATION, "An ai profile named '%s' already exists!  The new one will not be created.\n", profile_name);
				}
				else
				{
					// make sure we're under the limit
					if (Num_ai_profiles >= MAX_AI_PROFILES)
					{
						Warning(LOCATION, "Too many profiles in ai_profiles.tbl!  Max is %d.\n", MAX_AI_PROFILES - 1);	// -1 because one is built-in
						skip_to_string("#End", NULL);
						break;
					}

					profile = &Ai_profiles[Num_ai_profiles];
					Num_ai_profiles++;
				}
			}

			// initialize profile if we're not building from a previously parsed one
			if (!no_create)
			{
				// base profile, so zero it out
				if (profile == &Ai_profiles[0])
				{
					memset(profile, 0, sizeof(ai_profile_t));
				}
				// brand new profile, so set it to the base defaults
				else
				{
					memcpy(profile, &Ai_profiles[0], sizeof(ai_profile_t));
				}
			}

			// set the name
			strcpy_s(profile->profile_name, profile_name);


			// fill in any and all settings; they're all optional and can be in any order
			while (!check_for_string("$Profile Name:") && !check_for_string("#End"))
			{
				if (optional_string("$Player Afterburner Recharge Scale:"))
					parse_float_list(profile->afterburner_recharge_scale, NUM_SKILL_LEVELS);

				if (optional_string("$Max Beam Friendly Fire Damage:"))
					parse_float_list(profile->beam_friendly_damage_cap, NUM_SKILL_LEVELS);

				if (optional_string("$Player Countermeasure Life Scale:"))
					parse_float_list(profile->cmeasure_life_scale, NUM_SKILL_LEVELS);

				if (optional_string("$AI Countermeasure Firing Chance:"))
					parse_float_list(profile->cmeasure_fire_chance, NUM_SKILL_LEVELS);

				if (optional_string("$AI In Range Time:"))
					parse_float_list(profile->in_range_time, NUM_SKILL_LEVELS);

				if (optional_string("$AI Always Links Ammo Weapons:"))
					parse_float_list(profile->link_ammo_levels_always, NUM_SKILL_LEVELS);

				if (optional_string("$AI Maybe Links Ammo Weapons:"))
					parse_float_list(profile->link_ammo_levels_maybe, NUM_SKILL_LEVELS);

				if (optional_string("$Primary Ammo Burst Multiplier:"))
					parse_float_list(profile->primary_ammo_burst_mult, NUM_SKILL_LEVELS);

				if (optional_string("$AI Always Links Energy Weapons:"))
					parse_float_list(profile->link_energy_levels_always, NUM_SKILL_LEVELS);

				if (optional_string("$AI Maybe Links Energy Weapons:"))
					parse_float_list(profile->link_energy_levels_maybe, NUM_SKILL_LEVELS);

				if (optional_string("$Max Missles Locked on Player:") || optional_string("$Max Missiles Locked on Player:"))
					parse_int_list(profile->max_allowed_player_homers, NUM_SKILL_LEVELS);

				if (optional_string("$Max Player Attackers:"))
					parse_int_list(profile->max_attackers, NUM_SKILL_LEVELS);

				if (optional_string("$Max Incoming Asteroids:"))
					parse_int_list(profile->max_incoming_asteroids, NUM_SKILL_LEVELS);

				if (optional_string("$Player Damage Factor:") || optional_string("$AI Damage Reduction to Player Hull:"))
					parse_float_list(profile->player_damage_scale, NUM_SKILL_LEVELS);

				if (optional_string("$Player Subsys Damage Factor:") || optional_string("$AI Damage Reduction to Player Subsys:"))
					parse_float_list(profile->subsys_damage_scale, NUM_SKILL_LEVELS);

				// represented in fractions of F1_0
				if (optional_string("$Predict Position Delay:"))
				{
					int iLoop;
					float temp_list[NUM_SKILL_LEVELS];

					parse_float_list(temp_list, NUM_SKILL_LEVELS);

					for (iLoop = 0; iLoop < NUM_SKILL_LEVELS; iLoop++)
						profile->predict_position_delay[iLoop] = fl2f(temp_list[iLoop]);
				}

				if (optional_string("$AI Shield Manage Delay:") || optional_string("$AI Shield Manage Delays:"))
					parse_float_list(profile->shield_manage_delay, NUM_SKILL_LEVELS);

				if (optional_string("$Friendly AI Fire Delay Scale:"))
					parse_float_list(profile->ship_fire_delay_scale_friendly, NUM_SKILL_LEVELS);

				if (optional_string("$Hostile AI Fire Delay Scale:"))
					parse_float_list(profile->ship_fire_delay_scale_hostile, NUM_SKILL_LEVELS);

				if (optional_string("$Friendly AI Secondary Fire Delay Scale:"))
					parse_float_list(profile->ship_fire_secondary_delay_scale_friendly, NUM_SKILL_LEVELS);

				if (optional_string("$Hostile AI Secondary Fire Delay Scale:"))
					parse_float_list(profile->ship_fire_secondary_delay_scale_hostile, NUM_SKILL_LEVELS);

				if (optional_string("$AI Turn Time Scale:"))
					parse_float_list(profile->turn_time_scale, NUM_SKILL_LEVELS);

				if (optional_string("$Glide Attack Percent:")) {
					parse_float_list(profile->glide_attack_percent, NUM_SKILL_LEVELS);
					//Percent is nice for modders, but here in the code we want it betwwen 0 and 1.0
					//While we're at it, verify the range
					for (i = 0; i < NUM_SKILL_LEVELS; i++) {
						if (profile->glide_attack_percent[i] < 0.0f || profile->glide_attack_percent[i] > 100.0f) {
							Warning(LOCATION, "$Glide Attack Percent should be between 0 and 100.0 (read %f). Setting to 0.", profile->glide_attack_percent[i]);
							profile->glide_attack_percent[i] = 0.0f;
						}
						profile->glide_attack_percent[i] /= 100.0;
					}
				}

				if (optional_string("$Circle Strafe Percent:")) {
					parse_float_list(profile->circle_strafe_percent, NUM_SKILL_LEVELS);
					//Percent is nice for modders, but here in the code we want it betwwen 0 and 1.0
					//While we're at it, verify the range
					for (i = 0; i < NUM_SKILL_LEVELS; i++) {
						if (profile->circle_strafe_percent[i] < 0.0f || profile->circle_strafe_percent[i] > 100.0f) {
							Warning(LOCATION, "$Circle Strafe Percent should be between 0 and 100.0 (read %f). Setting to 0.", profile->circle_strafe_percent[i]);
							profile->circle_strafe_percent[i] = 0.0f;
						}
						profile->circle_strafe_percent[i] /= 100.0;
					}
				}

				if (optional_string("$Glide Strafe Percent:")) {
					parse_float_list(profile->glide_strafe_percent, NUM_SKILL_LEVELS);
					//Percent is nice for modders, but here in the code we want it betwwen 0 and 1.0
					//While we're at it, verify the range
					for (i = 0; i < NUM_SKILL_LEVELS; i++) {
						if (profile->glide_strafe_percent[i] < 0.0f || profile->glide_strafe_percent[i] > 100.0f) {
							Warning(LOCATION, "$Glide Strafe Percent should be between 0 and 100.0 (read %f). Setting to 0.", profile->glide_strafe_percent[i]);
							profile->glide_strafe_percent[i] = 0.0f;
						}
						profile->glide_strafe_percent[i] /= 100.0;
					}
				}

				if (optional_string("$Random Sidethrust Percent:")) {
					parse_float_list(profile->random_sidethrust_percent, NUM_SKILL_LEVELS);
					//Percent is nice for modders, but here in the code we want it betwwen 0 and 1.0
					//While we're at it, verify the range
					for (i = 0; i < NUM_SKILL_LEVELS; i++) {
						if (profile->random_sidethrust_percent[i] < 0.0f || profile->random_sidethrust_percent[i] > 100.0f) {
							Warning(LOCATION, "$Random Sidethrust Percent should be between 0 and 100.0 (read %f). Setting to 0.", profile->random_sidethrust_percent[i]);
							profile->random_sidethrust_percent[i] = 0.0f;
						}
						profile->random_sidethrust_percent[i] /= 100.0;
					}
				}

				if (optional_string("$Stalemate Time Threshold:"))
					parse_float_list(profile->stalemate_time_thresh, NUM_SKILL_LEVELS);

				if (optional_string("$Stalemate Distance Threshold:"))
					parse_float_list(profile->stalemate_dist_thresh, NUM_SKILL_LEVELS);

				if (optional_string("$Player Shield Recharge Scale:"))
					parse_float_list(profile->shield_energy_scale, NUM_SKILL_LEVELS);

				if (optional_string("$Player Weapon Recharge Scale:"))
					parse_float_list(profile->weapon_energy_scale, NUM_SKILL_LEVELS);

				if (optional_string("$Max Turret Target Ownage:"))
					parse_int_list(profile->max_turret_ownage_target, NUM_SKILL_LEVELS);

				if (optional_string("$Max Turret Player Ownage:"))
					parse_int_list(profile->max_turret_ownage_player, NUM_SKILL_LEVELS);

				if (optional_string("$Percentage Required For Kill Scale:"))
					parse_float_list(profile->kill_percentage_scale, NUM_SKILL_LEVELS);

				if (optional_string("$Percentage Required For Assist Scale:"))
					parse_float_list(profile->assist_percentage_scale, NUM_SKILL_LEVELS);

				if (optional_string("$Percentage Awarded For Capship Assist:"))
					parse_float_list(profile->assist_award_percentage_scale, NUM_SKILL_LEVELS);

				if (optional_string("$Repair Penalty:"))
					parse_int_list(profile->repair_penalty, NUM_SKILL_LEVELS);

				if (optional_string("$Delay Before Allowing Bombs to Be Shot Down:"))
					parse_float_list(profile->delay_bomb_arm_timer, NUM_SKILL_LEVELS);

				if (optional_string("$Chance AI Has to Fire Missiles at Player:"))
					parse_int_list(profile->chance_to_use_missiles_on_plr, NUM_SKILL_LEVELS);

				if (optional_string("$Max Aim Update Delay:"))
					parse_float_list(profile->max_aim_update_delay, NUM_SKILL_LEVELS);

				if (optional_string("$Turret Max Aim Update Delay:"))
					parse_float_list(profile->turret_max_aim_update_delay, NUM_SKILL_LEVELS);

				if (optional_string("$Player Autoaim FOV:"))
				{
					float fov_list[NUM_SKILL_LEVELS];
					parse_float_list(fov_list, NUM_SKILL_LEVELS);
					for (i = 0; i < NUM_SKILL_LEVELS; i++)
					{
						//Enforce range
						if (fov_list[i] < 0.0f || fov_list[i] >= 360.0f)
						{
							Warning(LOCATION, "$Player Autoaim FOV should be >= 0 and < 360.0 (read %f). Setting to 0.", fov_list[i]);
							fov_list[i] = 0.0f;
						}

						//Convert units
						profile->player_autoaim_fov[i] = fov_list[i] * PI / 180.0f;
					}
				}

				if (optional_string("$Detail Distance Multiplier:"))
					parse_float_list(profile->detail_distance_mult, NUM_SKILL_LEVELS);

				set_flag(profile, "$big ships can attack beam turrets on untargeted ships:", AIPF_BIG_SHIPS_CAN_ATTACK_BEAM_TURRETS_ON_UNTARGETED_SHIPS, AIP_FLAG);

				set_flag(profile, "$smart primary weapon selection:", AIPF_SMART_PRIMARY_WEAPON_SELECTION, AIP_FLAG);

				set_flag(profile, "$smart secondary weapon selection:", AIPF_SMART_SECONDARY_WEAPON_SELECTION, AIP_FLAG);

				set_flag(profile, "$smart shield management:", AIPF_SMART_SHIELD_MANAGEMENT, AIP_FLAG);

				set_flag(profile, "$smart afterburner management:", AIPF_SMART_AFTERBURNER_MANAGEMENT, AIP_FLAG);

				set_flag(profile, "$allow rapid secondary dumbfire:", AIPF_ALLOW_RAPID_SECONDARY_DUMBFIRE, AIP_FLAG);

				set_flag(profile, "$huge turret weapons ignore bombs:", AIPF_HUGE_TURRET_WEAPONS_IGNORE_BOMBS, AIP_FLAG);

				set_flag(profile, "$don't insert random turret fire delay:", AIPF_DONT_INSERT_RANDOM_TURRET_FIRE_DELAY, AIP_FLAG);

				set_flag(profile, "$hack improve non-homing swarm turret fire accuracy:", AIPF_HACK_IMPROVE_NON_HOMING_SWARM_TURRET_FIRE_ACCURACY, AIP_FLAG);

				set_flag(profile, "$shockwaves damage small ship subsystems:", AIPF_SHOCKWAVES_DAMAGE_SMALL_SHIP_SUBSYSTEMS, AIP_FLAG);

				set_flag(profile, "$navigation subsystem governs warpout capability:", AIPF_NAVIGATION_SUBSYS_GOVERNS_WARP, AIP_FLAG);

				set_flag(profile, "$ignore lower bound for minimum speed of docked ship:", AIPF_NO_MIN_DOCK_SPEED_CAP, AIP_FLAG);

				set_flag(profile, "$disable linked fire penalty:", AIPF_DISABLE_LINKED_FIRE_PENALTY, AIP_FLAG);

				set_flag(profile, "$disable weapon damage scaling:", AIPF_DISABLE_WEAPON_DAMAGE_SCALING, AIP_FLAG);

				set_flag(profile, "$use additive weapon velocity:", AIPF_USE_ADDITIVE_WEAPON_VELOCITY, AIP_FLAG);

				set_flag(profile, "$use newtonian dampening:", AIPF_USE_NEWTONIAN_DAMPENING, AIP_FLAG);

				set_flag(profile, "$include beams for kills and assists:", AIPF_INCLUDE_BEAMS_IN_STAT_CALCS, AIP_FLAG);

				set_flag(profile, "$score kills based on damage caused:", AIPF_KILL_SCORING_SCALES_WITH_DAMAGE, AIP_FLAG);

				set_flag(profile, "$score assists based on damage caused:", AIPF_ASSIST_SCORING_SCALES_WITH_DAMAGE, AIP_FLAG);

				set_flag(profile, "$allow event and goal scoring in multiplayer:", AIPF_ALLOW_MULTI_EVENT_SCORING, AIP_FLAG);

				set_flag(profile, "$fix linked primary weapon decision bug:", AIPF_FIX_LINKED_PRIMARY_BUG, AIP_FLAG);

				set_flag(profile, "$prevent turrets targeting too distant bombs:", AIPF_PREVENT_TARGETING_BOMBS_BEYOND_RANGE, AIP_FLAG);

				set_flag(profile, "$smart subsystem targeting for turrets:", AIPF_SMART_SUBSYSTEM_TARGETING_FOR_TURRETS, AIP_FLAG);

				set_flag(profile, "$fix heat seekers homing on stealth ships bug:", AIPF_FIX_HEAT_SEEKER_STEALTH_BUG, AIP_FLAG);

				set_flag(profile, "$multi allow empty primaries:", AIPF_MULTI_ALLOW_EMPTY_PRIMARIES, AIP_FLAG);

				set_flag(profile, "$multi allow empty secondaries:", AIPF_MULTI_ALLOW_EMPTY_SECONDARIES, AIP_FLAG);

				set_flag(profile, "$allow turrets target weapons freely:", AIPF_ALLOW_TURRETS_TARGET_WEAPONS_FREELY, AIP_FLAG);

				set_flag(profile, "$use only single fov for turrets:", AIPF_USE_ONLY_SINGLE_FOV_FOR_TURRETS, AIP_FLAG);

				set_flag(profile, "$allow vertical dodge:", AIPF_ALLOW_VERTICAL_DODGE, AIP_FLAG);

				set_flag(profile, "$force beam turrets to use normal fov:", AIPF_FORCE_BEAM_TURRET_FOV, AIP_FLAG);

				set_flag(profile, "$fix ai class bug:", AIPF_FIX_AI_CLASS_BUG, AIP_FLAG);

				set_flag(profile, "$turrets ignore targets radius in range checks:", AIPF2_TURRETS_IGNORE_TARGET_RADIUS, AIP_FLAG2);

				set_flag(profile, "$no extra collision avoidance vs player:", AIPF2_NO_SPECIAL_PLAYER_AVOID, AIP_FLAG2);

				set_flag(profile, "$perform fewer checks for death screams:", AIPF2_PERFORM_FEWER_SCREAM_CHECKS, AIP_FLAG2);

				set_flag(profile, "$advanced turret fov edge checks:", AIPF2_ADVANCED_TURRET_FOV_EDGE_CHECKS, AIP_FLAG2);

				set_flag(profile, "$require turrets to have target in fov:", AIPF2_REQUIRE_TURRET_TO_HAVE_TARGET_IN_FOV, AIP_FLAG2);

				set_flag(profile, "$all ships manage shields:", AIPF2_ALL_SHIPS_MANAGE_SHIELDS, AIP_FLAG2);

				set_flag(profile, "$ai aims from ship center:", AIPF2_AI_AIMS_FROM_SHIP_CENTER, AIP_FLAG2);

				set_flag(profile, "$allow primary link at mission start:", AIPF2_ALLOW_PRIMARY_LINK_AT_START, AIP_FLAG2);

				set_flag(profile, "$allow beams to damage bombs:", AIPF2_BEAMS_DAMAGE_WEAPONS, AIP_FLAG2);

				set_flag(profile, "$disable weapon damage scaling for player:", AIPF2_PLAYER_WEAPON_SCALE_FIX, AIP_FLAG2);

				set_flag(profile, "$countermeasures affect aspect seekers:", AIPF2_ASPECT_LOCK_COUNTERMEASURE, AIP_FLAG2);

				set_flag(profile, "$ai guards specific ship in wing:", AIPF2_AI_GUARDS_SPECIFIC_SHIP_IN_WING, AIP_FLAG2);

				profile->ai_path_mode = AI_PATH_MODE_NORMAL;
				if (optional_string("$ai path mode:"))
				{
					stuff_string(buf, F_NAME, NAME_LENGTH);
					int j = ai_path_type_match(buf);
					if (j >= 0) {
						profile->ai_path_mode = j;
					}
					else {
						Warning(LOCATION, "Invalid ai path mode '%s' specified", buf);
					}
				}

				set_flag(profile, "$no warp camera:", AIPF2_NO_WARP_CAMERA, AIP_FLAG2);

				set_flag(profile, "$fix ai path order bug:", AIPF2_FIX_AI_PATH_ORDER_BUG, AIP_FLAG2);

				set_flag(profile, "$strict turret-tagged-only targeting:", AIPF2_STRICT_TURRET_TAGGED_ONLY_TARGETING, AIP_FLAG2);

				set_flag(profile, "$aspect bomb invulnerability fix:", AIPF2_ASPECT_INVULNERABILITY_FIX, AIP_FLAG2);

				set_flag(profile, "$glide decay requires thrust:", AIPF2_GLIDE_DECAY_REQUIRES_THRUST, AIP_FLAG2);

				set_flag(profile, "$ai can slow down when attacking big ships:", AIPF2_AI_CAN_SLOW_DOWN_ATTACKING_BIG_SHIPS, AIP_FLAG2);

				profile->bay_arrive_speed_mult = 1.0f;
				profile->bay_depart_speed_mult = 1.0f;
				if (optional_string("$bay arrive speed multiplier:")) {
					stuff_float(&profile->bay_arrive_speed_mult);
				}
				if (optional_string("$bay depart speed multiplier:")) {
					stuff_float(&profile->bay_depart_speed_mult);
				}

				// ----------

				// compatibility
				if (optional_string("$perform less checks for death screams:"))
				{
					mprintf(("Warning: \"$perform less checks for death screams\" flag is deprecated in favor of \"$perform fewer checks for death screams\"\n"));
					bool temp;
					stuff_boolean(&temp);
					if (temp)
						profile->flags2 |= AIPF2_PERFORM_FEWER_SCREAM_CHECKS;
					else
						profile->flags2 &= ~AIPF2_PERFORM_FEWER_SCREAM_CHECKS;
				}
				if (optional_string("$allow primary link delay:"))
				{
					mprintf(("Warning: \"$allow primary link delay\" flag is deprecated in favor of \"$allow primary link at mission start\"\n"));
					bool temp;
					stuff_boolean(&temp);
					if (temp)
						profile->flags2 &= ~AIPF2_ALLOW_PRIMARY_LINK_AT_START;
					else
						profile->flags2 |= AIPF2_ALLOW_PRIMARY_LINK_AT_START;
				}


				// if we've been through once already and are at the same place, force a move
				if (saved_Mp && (saved_Mp == Mp))
				{
					char tmp[60];
					memset(tmp, 0, 60);
					strncpy(tmp, Mp, 59);
					mprintf(("WARNING: Unrecognized parameter in ai_profiles: %s\n", tmp));

					Mp++;
				}

				// find next valid option
				skip_to_start_of_string_either("$", "#");
				saved_Mp = Mp;
			}
		}

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

	// add tbl/tbm to multiplayer validation list
	extern void fs2netd_add_table_validation(const char *tblname);
	fs2netd_add_table_validation(filename);
}