Index: code/ship/ship.cpp
===================================================================
--- code/ship/ship.cpp	(revision 11204)
+++ code/ship/ship.cpp	(working copy)
@@ -162,6 +162,7 @@
 
 ship_info		Ship_info[MAX_SHIP_CLASSES];
 reinforcements	Reinforcements[MAX_REINFORCEMENTS];
+SCP_vector<ship_info>	Ship_templates;
 
 static char **tspecies_names = NULL;
 
@@ -655,7 +656,325 @@
 #define SHIP_MULTITEXT_LENGTH 4096
 #define DEFAULT_DELTA_BANK_CONST	0.5f
 
+#define CHECK_THEN_COPY(attribute) \
+do {\
+	if (other.attribute != NULL)\
+		attribute = vm_strdup( other.attribute );\
+} while(false)
 
+void ship_info::clone(const ship_info& other)
+{
+	strcpy_s(name, other.name);
+	strcpy_s(alt_name, other.alt_name);
+	strcpy_s(short_name, other.short_name);
+	species = other.species;
+	class_type = other.class_type;
+
+	CHECK_THEN_COPY(type_str);
+	CHECK_THEN_COPY(maneuverability_str);
+	CHECK_THEN_COPY(armor_str);
+	CHECK_THEN_COPY(manufacturer_str);
+	CHECK_THEN_COPY(desc);
+	CHECK_THEN_COPY(tech_desc);
+
+	strcpy_s(tech_title, other.tech_title);
+
+	CHECK_THEN_COPY(ship_length);
+	CHECK_THEN_COPY(gun_mounts);
+	CHECK_THEN_COPY(missile_banks);
+
+	strcpy_s(cockpit_pof_file, other.cockpit_pof_file);
+	cockpit_offset = other.cockpit_offset;
+	strcpy_s(pof_file, other.pof_file);
+	strcpy_s(pof_file_hud, other.pof_file_hud);
+	num_detail_levels = other.num_detail_levels;
+	memcpy(detail_distance, other.detail_distance, sizeof(int) * MAX_SHIP_DETAIL_LEVELS);
+
+	// I'm not sure if these three are A) a good idea or B) relevant at all. -MageKing17
+	cockpit_model_num = other.cockpit_model_num;
+	model_num = other.model_num;
+	model_num_hud = other.model_num_hud;
+
+	hud_target_lod = other.hud_target_lod;
+	density = other.density;
+	damp = other.damp;
+	rotdamp = other.rotdamp;
+	delta_bank_const = other.delta_bank_const;
+	max_vel = other.max_vel;
+	max_rotvel = other.max_rotvel;
+	rotation_time = other.rotation_time;
+	srotation_time = other.srotation_time;
+	max_rear_vel = other.max_rear_vel;
+	forward_accel = other.forward_accel;
+	forward_decel = other.forward_decel;
+	slide_accel = other.slide_accel;
+	slide_decel = other.slide_decel;
+
+	strcpy_s(warpin_anim, other.warpin_anim);
+	warpin_radius = other.warpin_radius;
+	warpin_snd_start = other.warpin_snd_start;
+	warpin_snd_end = other.warpin_snd_end;
+	warpin_speed = other.warpin_speed;
+	warpin_time = other.warpin_time;
+	warpin_decel_exp = other.warpin_decel_exp;
+	warpin_type = other.warpin_type;
+
+	strcpy_s(warpout_anim, other.warpout_anim);
+	warpout_radius = other.warpout_radius;
+	warpout_snd_start = other.warpout_snd_start;
+	warpout_snd_end = other.warpout_snd_end;
+	warpout_engage_time = other.warpout_engage_time;
+	warpout_speed = other.warpout_speed;
+	warpout_time = other.warpout_time;
+	warpout_accel_exp = other.warpout_accel_exp;
+	warpout_type = other.warpout_type;
+
+	warpout_player_speed = other.warpout_player_speed;
+
+	flags = other.flags;
+	flags2 = other.flags2;
+	ai_class = other.ai_class;
+	max_speed = other.max_speed;
+	min_speed = other.min_speed;
+	max_accel = other.max_accel;
+
+	collision_damage_type_idx = other.collision_damage_type_idx;
+	collision_physics = other.collision_physics;
+
+	memcpy(&shockwave, &other.shockwave, sizeof(shockwave_create_info));
+	explosion_propagates = other.explosion_propagates;
+	big_exp_visual_rad = other.big_exp_visual_rad;
+	prop_exp_rad_mult = other.prop_exp_rad_mult;
+	death_roll_r_mult = other.death_roll_r_mult;
+	death_fx_r_mult = other.death_fx_r_mult;
+	death_roll_time_mult = other.death_roll_time_mult;
+	death_roll_base_time = other.death_roll_base_time;
+	death_fx_count = other.death_fx_count;
+	shockwave_count = other.shockwave_count;
+	explosion_bitmap_anims = other.explosion_bitmap_anims;
+	vaporize_chance = other.vaporize_chance;
+
+	impact_spew = other.impact_spew;
+	damage_spew = other.damage_spew;
+	split_particles = other.split_particles;
+	knossos_end_particles = other.knossos_end_particles;
+	regular_end_particles = other.regular_end_particles;
+
+	debris_min_lifetime = other.debris_min_lifetime;
+	debris_max_lifetime = other.debris_max_lifetime;
+	debris_min_speed = other.debris_min_speed;
+	debris_max_speed = other.debris_max_speed;
+	debris_min_rotspeed = other.debris_min_rotspeed;
+	debris_max_rotspeed = other.debris_max_rotspeed;
+	debris_damage_type_idx = other.debris_damage_type_idx;
+	debris_min_hitpoints = other.debris_min_hitpoints;
+	debris_max_hitpoints = other.debris_max_hitpoints;
+	debris_damage_mult = other.debris_damage_mult;
+	debris_arc_percent = other.debris_arc_percent;
+
+	if ( other.n_subsystems > 0 ) {
+		if( n_subsystems < 1 ) {
+			subsystems = (model_subsystem *)vm_malloc(sizeof(model_subsystem) * other.n_subsystems );
+		} else {
+			subsystems = (model_subsystem *)vm_realloc(subsystems, sizeof(model_subsystem) * other.n_subsystems);
+		}
+		Assert( subsystems != NULL );
+
+		for ( int i = 0; i < other.n_subsystems; i++ ){
+			memcpy(&(subsystems[i]), &(other.subsystems[i]), sizeof(model_subsystem));
+		}
+	}
+	n_subsystems = other.n_subsystems;
+
+	power_output = other.power_output;
+	max_overclocked_speed = other.max_overclocked_speed;
+	max_weapon_reserve = other.max_weapon_reserve;
+	max_shield_regen_per_second = other.max_shield_regen_per_second;
+	max_weapon_regen_per_second = other.max_weapon_regen_per_second;
+
+	afterburner_max_vel = other.afterburner_max_vel;
+	afterburner_forward_accel = other.afterburner_forward_accel;
+	afterburner_fuel_capacity = other.afterburner_fuel_capacity;
+	afterburner_burn_rate = other.afterburner_burn_rate;
+	afterburner_recover_rate = other.afterburner_recover_rate;
+	afterburner_max_reverse_vel = other.afterburner_max_reverse_vel;
+	afterburner_reverse_accel = other.afterburner_reverse_accel;
+
+	cmeasure_type = other.cmeasure_type;
+	cmeasure_max = other.cmeasure_max;
+
+	num_primary_banks = other.num_primary_banks;
+	memcpy(primary_bank_weapons, other.primary_bank_weapons, sizeof(int) * MAX_SHIP_PRIMARY_BANKS);
+	memcpy(primary_bank_ammo_capacity, other.primary_bank_ammo_capacity, sizeof(int) * MAX_SHIP_PRIMARY_BANKS);
+
+	num_secondary_banks = other.num_secondary_banks;
+	memcpy(secondary_bank_weapons, other.secondary_bank_weapons, sizeof(int) * MAX_SHIP_SECONDARY_BANKS);
+	memcpy(secondary_bank_ammo_capacity, other.secondary_bank_ammo_capacity, sizeof(int) * MAX_SHIP_SECONDARY_BANKS);
+
+	memcpy(draw_primary_models, other.draw_primary_models, sizeof(bool) * MAX_SHIP_PRIMARY_BANKS);
+	memcpy(draw_secondary_models, other.draw_secondary_models, sizeof(bool) * MAX_SHIP_SECONDARY_BANKS);
+	weapon_model_draw_distance = other.weapon_model_draw_distance;
+
+	max_hull_strength = other.max_hull_strength;
+	max_shield_strength = other.max_shield_strength;
+	auto_shield_spread = other.auto_shield_spread;
+	auto_shield_spread_bypass = other.auto_shield_spread_bypass;
+	auto_shield_spread_from_lod = other.auto_shield_spread_from_lod;
+
+	// ...Hmm. A memcpy() seems slightly overkill here, but I've settled into the pattern of "array gets memcpy'd", so... -MageKing17
+	memcpy(shield_point_augment_ctrls, other.shield_point_augment_ctrls, sizeof(int) * 4);
+
+	hull_repair_rate = other.hull_repair_rate;
+	subsys_repair_rate = other.subsys_repair_rate;
+
+	sup_hull_repair_rate = other.sup_hull_repair_rate;
+	sup_shield_repair_rate = other.sup_shield_repair_rate;
+	sup_subsys_repair_rate = other.sup_subsys_repair_rate;
+
+	closeup_pos = other.closeup_pos;
+	closeup_zoom = other.closeup_zoom;
+
+	memcpy(allowed_weapons, other.allowed_weapons, sizeof(int) * MAX_WEAPON_TYPES);
+
+	memcpy(restricted_loadout_flag, other.restricted_loadout_flag, sizeof(int) * MAX_SHIP_WEAPONS);
+	memcpy(allowed_bank_restricted_weapons, other.allowed_bank_restricted_weapons, sizeof(int) * MAX_SHIP_WEAPONS * MAX_WEAPON_TYPES);
+
+	shield_icon_index = other.shield_icon_index;
+	strcpy_s(icon_filename, other.icon_filename);
+	strcpy_s(anim_filename, other.anim_filename);
+	strcpy_s(overhead_filename, other.overhead_filename);
+	selection_effect = other.selection_effect;
+
+	bii_index_ship = other.bii_index_ship;
+	bii_index_ship_with_cargo = other.bii_index_ship_with_cargo;
+	bii_index_wing = other.bii_index_wing;
+	bii_index_wing_with_cargo = other.bii_index_wing_with_cargo;
+
+	score = other.score;
+
+	scan_time = other.scan_time;
+
+	memcpy(ct_info, other.ct_info, sizeof(trail_info) * MAX_SHIP_CONTRAILS);
+	ct_count = other.ct_count;
+
+	num_nondark_colors = other.num_nondark_colors;
+	memcpy(nondark_colors, other.nondark_colors, sizeof(ubyte) * 3);
+
+	memcpy(shield_color, other.shield_color, sizeof(ubyte) * 3);
+
+	uses_team_colors = other.uses_team_colors;
+	default_team_name = other.default_team_name;
+
+	afterburner_trail = other.afterburner_trail;
+	afterburner_trail_width_factor = other.afterburner_trail_width_factor;
+	afterburner_trail_alpha_factor = other.afterburner_trail_alpha_factor;
+	afterburner_trail_life = other.afterburner_trail_life;
+	afterburner_trail_faded_out_sections = other.afterburner_trail_faded_out_sections;
+
+	normal_thruster_particles = other.normal_thruster_particles;
+	afterburner_thruster_particles = other.afterburner_thruster_particles;
+
+	memcpy(&thruster_flame_info, &other.thruster_flame_info, sizeof(thrust_pair));
+	memcpy(&thruster_glow_info, &other.thruster_glow_info, sizeof(thrust_pair));
+	memcpy(&thruster_secondary_glow_info, &other.thruster_secondary_glow_info, sizeof(thrust_pair_bitmap));
+	memcpy(&thruster_tertiary_glow_info, &other.thruster_tertiary_glow_info, sizeof(thrust_pair_bitmap));
+	memcpy(&thruster_distortion_info, &other.thruster_distortion_info, sizeof(thrust_pair_bitmap));
+
+	thruster01_glow_rad_factor = other.thruster01_glow_rad_factor;
+	thruster02_glow_rad_factor = other.thruster02_glow_rad_factor;
+	thruster03_glow_rad_factor = other.thruster03_glow_rad_factor;
+	thruster02_glow_len_factor = other.thruster02_glow_len_factor;
+	thruster_dist_rad_factor = other.thruster_dist_rad_factor;
+	thruster_dist_len_factor = other.thruster_dist_len_factor;
+
+	draw_distortion = other.draw_distortion;
+
+	splodeing_texture = other.splodeing_texture;
+	strcpy_s(splodeing_texture_name, other.splodeing_texture_name);
+
+	replacement_textures = other.replacement_textures;
+
+	armor_type_idx = other.armor_type_idx;
+	shield_armor_type_idx = other.shield_armor_type_idx;
+	
+	can_glide = other.can_glide;
+	glide_cap = other.glide_cap;
+	glide_dynamic_cap = other.glide_dynamic_cap;
+	glide_accel_mult = other.glide_accel_mult;
+	use_newtonian_damp = other.use_newtonian_damp;
+	newtonian_damp_override = other.newtonian_damp_override;
+
+	autoaim_fov = other.autoaim_fov;
+
+	topdown_offset_def = other.topdown_offset_def;
+	topdown_offset = other.topdown_offset;
+
+	engine_snd = other.engine_snd;
+	glide_start_snd = other.glide_start_snd;
+	glide_end_snd = other.glide_end_snd;
+
+	ship_sounds = other.ship_sounds;
+
+	num_maneuvering = other.num_maneuvering;
+	memcpy(maneuvering, other.maneuvering, sizeof(man_thruster) * MAX_MAN_THRUSTERS);
+
+	radar_image_2d_idx = other.radar_image_2d_idx;
+	radar_color_image_2d_idx = other.radar_color_image_2d_idx;
+	radar_image_size = other.radar_image_size;
+	radar_projection_size_mult = other.radar_projection_size_mult;
+
+	memcpy(ship_iff_info, other.ship_iff_info, sizeof(int) * MAX_IFFS * MAX_IFFS);
+
+	aiming_flags = other.aiming_flags;
+	minimum_convergence_distance = other.minimum_convergence_distance;
+	convergence_distance = other.convergence_distance;
+	convergence_offset = other.convergence_offset;
+
+	emp_resistance_mod = other.emp_resistance_mod;
+
+	piercing_damage_draw_limit = other.piercing_damage_draw_limit;
+
+	damage_lightning_type = other.damage_lightning_type;
+
+	hud_gauges = other.hud_gauges;
+	hud_enabled = other.hud_enabled;
+	hud_retail = other.hud_retail;
+
+	displays = other.displays;
+
+	pathMetadata = other.pathMetadata;
+}
+
+#define CHECK_THEN_FREE(attribute) \
+do {\
+	if (attribute != NULL) {\
+		vm_free(attribute);\
+		attribute = NULL;\
+	}\
+} while(false)
+
+void ship_info::free_strings()
+{
+	CHECK_THEN_FREE(type_str);
+	CHECK_THEN_FREE(maneuverability_str);
+	CHECK_THEN_FREE(armor_str);
+	CHECK_THEN_FREE(manufacturer_str);
+	CHECK_THEN_FREE(desc);
+	CHECK_THEN_FREE(tech_desc);
+	CHECK_THEN_FREE(ship_length);
+	CHECK_THEN_FREE(gun_mounts);
+	CHECK_THEN_FREE(missile_banks);
+}
+
+const ship_info &ship_info::operator= (const ship_info& other)
+{
+	if (this != &other) {
+		free_strings();
+		clone(other);
+	}
+	return *this;
+}
+
 /**
  * Writes default info to a ship entry
  * 
@@ -1101,14 +1420,90 @@
 
 	// Use a template for this ship.
 	if( optional_string( "+Use Template:" ) ) {
-		Warning(LOCATION, "Ignoring '+Use Template' field for '%s'.  Ship templates have been broken since they were added, and are not currently supported.", sip->name);
+		if ( !create_if_not_found ) {
+			Warning(LOCATION, "Both '+nocreate' and '+Use Template:' were specified for ship class '%s', ignoring '+Use Template:'\n", buf);
+		}
+		else {
+			char template_name[SHIP_MULTITEXT_LENGTH];
+			stuff_string(template_name, F_NAME, SHIP_MULTITEXT_LENGTH);
+			int template_id = ship_template_lookup(template_name);
+			if ( template_id != -1 ) {
+				first_time = false;
+				*sip = Ship_templates[template_id];
+				strcpy_s(sip->name, buf);
+			}
+			else {
+				Warning(LOCATION, "Unable to find ship template '%s' requested by ship class '%s', ignoring template request...", template_name, buf);
+			}
+		}
 	}
 
-	rtn = parse_ship_values(sip, first_time, replace);
+	rtn = parse_ship_values(sip, false, first_time, replace);
 
 	return rtn;	//0 for success
 }
 
+/**
+ * Parse the information for a specific ship type template.
+ */
+int parse_ship_template()
+{
+	char buf[SHIP_MULTITEXT_LENGTH];
+	ship_info *sip;
+	int rtn = 0;
+	bool first_time = false;
+
+	required_string("$Template:");
+	stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
+	
+	if( optional_string("+nocreate") ) {
+		Warning(LOCATION, "+nocreate flag used on ship template. Ship templates can not be modified. Ignoring +nocreate.");
+	}
+	
+	diag_printf ("Ship template name -- %s\n", buf);
+	//Check if the template exists already
+	int template_id;
+	template_id = ship_template_lookup( buf );
+	
+	if( template_id != -1 ) {
+		sip = &Ship_templates[template_id];
+		Warning(LOCATION, "WARNING: Ship template %s already exists. All ship template names must be unique.", sip->name);
+		if ( !skip_to_start_of_string_either("$Template:", "#End")) {
+			error_display(1, "Missing [#End] or [$Template] after duplicate entry for %s", sip->name);
+		}
+		return -1;
+	}
+	else {
+		
+		Ship_templates.push_back(ship_info());
+		sip = &Ship_templates.back();
+		
+		init_ship_entry(sip);
+		strcpy_s(sip->name, buf);
+		//Use another template for this template. This allows for template hierarchies. - Turey
+		if( optional_string("+Use Template:") ) {
+			char template_name[SHIP_MULTITEXT_LENGTH];
+			stuff_string(template_name, F_NAME, SHIP_MULTITEXT_LENGTH);
+			template_id = ship_template_lookup( template_name);
+			
+			if ( template_id != -1 ) {
+				first_time = false;
+				*sip = Ship_templates[template_id];
+				strcpy_s(sip->name, buf);
+			}
+			else {
+				Warning(LOCATION, "Unable to find ship template '%s' requested by ship template '%s', ignoring template request...", template_name, buf);
+			}
+		}
+	}
+	
+	rtn = parse_ship_values( sip, true, first_time, false );
+	
+	Assertion(ship_template_lookup(sip->name) == -1, "Somehow, despite checking for duplicate templates before parsing, we wound up with one (%s) anyway. Get a coder!\n", sip->name);
+	
+	return rtn;
+}
+
 void parse_ship_sound(char *name, GameSoundsIndex id, ship_info *sip)
 {
 	Assert( name != NULL );
@@ -1349,13 +1744,24 @@
 /**
  * Puts values into a ship_info.
  */
-int parse_ship_values(ship_info* sip, bool first_time, bool replace)
+int parse_ship_values(ship_info* sip, const bool is_template, const bool first_time, const bool replace)
 {
 	char buf[SHIP_MULTITEXT_LENGTH];
+	char* info_type_name;
+	char* type_name;
 	int i, j, num_allowed;
 	int allowed_weapons[MAX_WEAPON_TYPES];
 	int rtn = 0;
 	char name_tmp[NAME_LENGTH];
+
+	if ( ! is_template ) {
+		info_type_name = "Ship Class";
+		type_name = "$Name";
+	}
+	else {
+		info_type_name = "Ship Template";
+		type_name = "$Template";
+	}
 	
 	if(optional_string("$Alt name:"))
 		stuff_string(sip->alt_name, F_NAME, NAME_LENGTH);
@@ -1362,14 +1768,13 @@
 
 	if(optional_string("$Short name:"))
 		stuff_string(sip->short_name, F_NAME, NAME_LENGTH);
-	else if(first_time)
+	else if (first_time)
 	{
-		char *srcpos, *srcend, *destpos, *destend;
+		char *srcpos, *srcend, *destpos;
 		srcpos = sip->name;
 		destpos = sip->short_name;
 		srcend = srcpos + strlen(sip->name);
-		destend = destpos + sizeof(sip->short_name) - 1;
-		while(srcpos < srcend)
+		while(srcpos <= srcend)
 		{
 			if(*srcpos != ' ')
 				*destpos++ = *srcpos++;
@@ -1769,32 +2174,32 @@
 		if(optional_string("+Min Lifetime:"))	{
 			stuff_float(&sip->debris_min_lifetime);
 			if(sip->debris_min_lifetime < 0.0f)
-				Warning(LOCATION, "Debris min lifetime on ship class '%s' is below 0 and will be ignored", sip->name);
+				Warning(LOCATION, "Debris min lifetime on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
 		}
 		if(optional_string("+Max Lifetime:"))	{
 			stuff_float(&sip->debris_max_lifetime);
 			if(sip->debris_max_lifetime < 0.0f)
-				Warning(LOCATION, "Debris max lifetime on ship class '%s' is below 0 and will be ignored", sip->name);
+				Warning(LOCATION, "Debris max lifetime on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
 		}
 		if(optional_string("+Min Speed:"))	{
 			stuff_float(&sip->debris_min_speed);
 			if(sip->debris_min_speed < 0.0f)
-				Warning(LOCATION, "Debris min speed on ship class '%s' is below 0 and will be ignored", sip->name);
+				Warning(LOCATION, "Debris min speed on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
 		}
 		if(optional_string("+Max Speed:"))	{
 			stuff_float(&sip->debris_max_speed);
 			if(sip->debris_max_speed < 0.0f)
-				Warning(LOCATION, "Debris max speed on ship class '%s' is below 0 and will be ignored", sip->name);
+				Warning(LOCATION, "Debris max speed on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
 		}
 		if(optional_string("+Min Rotation speed:"))	{
 			stuff_float(&sip->debris_min_rotspeed);
 			if(sip->debris_min_rotspeed < 0.0f)
-				Warning(LOCATION, "Debris min speed on ship class '%s' is below 0 and will be ignored", sip->name);
+				Warning(LOCATION, "Debris min speed on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
 		}
 		if(optional_string("+Max Rotation speed:"))	{
 			stuff_float(&sip->debris_max_rotspeed);
 			if(sip->debris_max_rotspeed < 0.0f)
-				Warning(LOCATION, "Debris max speed on ship class '%s' is below 0 and will be ignored", sip->name);
+				Warning(LOCATION, "Debris max speed on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
 		}
 		if(optional_string("+Damage Type:")) {
 			stuff_string(buf, F_NAME, NAME_LENGTH);
@@ -1803,22 +2208,22 @@
 		if(optional_string("+Min Hitpoints:")) {
 			stuff_float(&sip->debris_min_hitpoints);
 			if(sip->debris_min_hitpoints < 0.0f)
-				Warning(LOCATION, "Debris min hitpoints on ship class '%s' is below 0 and will be ignored", sip->name);
+				Warning(LOCATION, "Debris min hitpoints on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
 		}
 		if(optional_string("+Max Hitpoints:")) {
 			stuff_float(&sip->debris_max_hitpoints);
 			if(sip->debris_max_hitpoints < 0.0f)
-				Warning(LOCATION, "Debris max hitpoints on ship class '%s' is below 0 and will be ignored", sip->name);
+				Warning(LOCATION, "Debris max hitpoints on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
 		}
 		if(optional_string("+Damage Multiplier:")) {
 			stuff_float(&sip->debris_damage_mult);
 			if(sip->debris_damage_mult < 0.0f)
-				Warning(LOCATION, "Debris damage multiplier on ship class '%s' is below 0 and will be ignored", sip->name);
+				Warning(LOCATION, "Debris damage multiplier on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
 		}
 		if(optional_string("+Lightning Arc Percent:")) {
 			stuff_float(&sip->debris_arc_percent);
 			if(sip->debris_arc_percent < 0.0f || sip->debris_arc_percent > 100.0f) {
-				Warning(LOCATION, "Lightning Arc Percent on ship class '%s' should be between 0 and 100.0 (read %f). Entry will be ignored.", sip->name, sip->debris_arc_percent);
+				Warning(LOCATION, "Lightning Arc Percent on %s '%s' should be between 0 and 100.0 (read %f). Entry will be ignored.", info_type_name, sip->name, sip->debris_arc_percent);
 				sip->debris_arc_percent = 50.0;
 			}
 			//Percent is nice for modders, but here in the code we want it betwwen 0 and 1.0
@@ -1828,19 +2233,19 @@
 	}
 	//WMC - sanity checking
 	if(sip->debris_min_speed > sip->debris_max_speed && sip->debris_max_speed >= 0.0f) {
-		Warning(LOCATION, "Debris min speed (%f) on ship class '%s' is greater than debris max speed (%f), and will be set to debris max speed.", sip->debris_min_speed, sip->name, sip->debris_max_speed);
+		Warning(LOCATION, "Debris min speed (%f) on %s '%s' is greater than debris max speed (%f), and will be set to debris max speed.", sip->debris_min_speed, info_type_name, sip->name, sip->debris_max_speed);
 		sip->debris_min_speed = sip->debris_max_speed;
 	}
 	if(sip->debris_min_rotspeed > sip->debris_max_rotspeed && sip->debris_max_rotspeed >= 0.0f) {
-		Warning(LOCATION, "Debris min rotation speed (%f) on ship class '%s' is greater than debris max rotation speed (%f), and will be set to debris max rotation speed.", sip->debris_min_rotspeed, sip->name, sip->debris_max_rotspeed);
+		Warning(LOCATION, "Debris min rotation speed (%f) on %s '%s' is greater than debris max rotation speed (%f), and will be set to debris max rotation speed.", sip->debris_min_rotspeed, info_type_name, sip->name, sip->debris_max_rotspeed);
 		sip->debris_min_rotspeed = sip->debris_max_rotspeed;
 	}
 	if(sip->debris_min_lifetime > sip->debris_max_lifetime && sip->debris_max_lifetime >= 0.0f) {
-		Warning(LOCATION, "Debris min lifetime (%f) on ship class '%s' is greater than debris max lifetime (%f), and will be set to debris max lifetime.", sip->debris_min_lifetime, sip->name, sip->debris_max_lifetime);
+		Warning(LOCATION, "Debris min lifetime (%f) on %s '%s' is greater than debris max lifetime (%f), and will be set to debris max lifetime.", sip->debris_min_lifetime, info_type_name, sip->name, sip->debris_max_lifetime);
 		sip->debris_min_lifetime = sip->debris_max_lifetime;
 	}
 	if(sip->debris_min_hitpoints > sip->debris_max_hitpoints && sip->debris_max_hitpoints >= 0.0f) {
-		Warning(LOCATION, "Debris min hitpoints (%f) on ship class '%s' is greater than debris max hitpoints (%f), and will be set to debris max hitpoints.", sip->debris_min_hitpoints, sip->name, sip->debris_max_hitpoints);
+		Warning(LOCATION, "Debris min hitpoints (%f) on %s '%s' is greater than debris max hitpoints (%f), and will be set to debris max hitpoints.", sip->debris_min_hitpoints, info_type_name, sip->name, sip->debris_max_hitpoints);
 		sip->debris_min_hitpoints = sip->debris_max_hitpoints;
 	}
 
@@ -1858,7 +2263,7 @@
 
 	if(optional_string("$Banking Constant:"))
 		stuff_float( &(sip->delta_bank_const) );
-	diag_printf ("Ship class '%s' delta_bank_const -- %7.3f\n", sip->name, sip->delta_bank_const);
+	diag_printf ("%s '%s' delta_bank_const -- %7.3f\n", info_type_name, sip->name, sip->delta_bank_const);
 
 	if(optional_string("$Max Velocity:"))
 	{
@@ -1875,7 +2280,7 @@
 
 		// div/0 safety check.
 		if ((sip->rotation_time.xyz.x == 0) || (sip->rotation_time.xyz.y == 0) || (sip->rotation_time.xyz.z == 0))
-			Warning(LOCATION, "Rotation time must have non-zero values in each of the three variables.\nFix this in ship %s\n", sip->name);
+			Warning(LOCATION, "Rotation time must have non-zero values in each of the three variables.\nFix this in %s %s\n", info_type_name, sip->name);
 
 		sip->srotation_time = (sip->rotation_time.xyz.x + sip->rotation_time.xyz.y)/2.0f;
 
@@ -1982,7 +2387,7 @@
 		if(j >= 0) {
 			sip->warpin_type = j;
 		} else {
-			Warning(LOCATION, "Invalid warpin type '%s' specified for ship '%s'", buf, sip->name);
+			Warning(LOCATION, "Invalid warpin type '%s' specified for %s '%s'", buf, info_type_name, sip->name);
 			sip->warpin_type = WT_DEFAULT;
 		}
 	}
@@ -2001,7 +2406,7 @@
 		stuff_float(&t_time);
 		sip->warpin_time = fl2i(t_time*1000.0f);
 		if(sip->warpin_time <= 0) {
-			Warning(LOCATION, "Warp-in time specified as 0 or less on ship '%s'; value ignored", sip->name);
+			Warning(LOCATION, "Warp-in time specified as 0 or less on %s '%s'; value ignored", info_type_name, sip->name);
 		}
 	}
 
@@ -2009,7 +2414,7 @@
 	{
 		stuff_float(&sip->warpin_decel_exp);
 		if (sip->warpin_decel_exp < 0.0f) {
-			Warning(LOCATION, "Warp-in deceleration exponent specified as less than 0 on ship '%s'; value ignored", sip->name);
+			Warning(LOCATION, "Warp-in deceleration exponent specified as less than 0 on %s '%s'; value ignored", info_type_name, sip->name);
 			sip->warpin_decel_exp = 1.0f;
 		}
 	}
@@ -2018,7 +2423,7 @@
 	{
 		stuff_float(&sip->warpin_radius);
 		if(sip->warpin_radius <= 0.0f) {
-			Warning(LOCATION, "Warp-in radius specified as 0 or less on ship '%s'; value ignored", sip->name);
+			Warning(LOCATION, "Warp-in radius specified as 0 or less on %s '%s'; value ignored", info_type_name, sip->name);
 		}
 	}
 
@@ -2034,7 +2439,7 @@
 		if(j >= 0) {
 			sip->warpout_type = j;
 		} else {
-			Warning(LOCATION, "Invalid warpout type '%s' specified for ship '%s'", buf, sip->name);
+			Warning(LOCATION, "Invalid warpout type '%s' specified for %s '%s'", buf, info_type_name, sip->name);
 			sip->warpout_type = WT_DEFAULT;
 		}
 	}
@@ -2049,7 +2454,7 @@
 		if (t_time >= 0)
 			sip->warpout_engage_time = fl2i(t_time*1000.0f);
 		else
-			Warning(LOCATION, "Warp-out engage time specified as 0 or less on ship '%s'; value ignored", sip->name);
+			Warning(LOCATION, "Warp-out engage time specified as 0 or less on %s '%s'; value ignored", info_type_name, sip->name);
 	} else {
 		sip->warpout_engage_time = -1;
 	}
@@ -2065,7 +2470,7 @@
 		stuff_float(&t_time);
 		sip->warpout_time = fl2i(t_time*1000.0f);
 		if(sip->warpout_time <= 0) {
-			Warning(LOCATION, "Warp-out time specified as 0 or less on ship '%s'; value ignored", sip->name);
+			Warning(LOCATION, "Warp-out time specified as 0 or less on %s '%s'; value ignored", info_type_name, sip->name);
 		}
 	}
 
@@ -2073,7 +2478,7 @@
 	{
 		stuff_float(&sip->warpout_accel_exp);
 		if (sip->warpout_accel_exp < 0.0f) {
-			Warning(LOCATION, "Warp-out acceleration exponent specified as less than 0 on ship '%s'; value ignored", sip->name);
+			Warning(LOCATION, "Warp-out acceleration exponent specified as less than 0 on %s '%s'; value ignored", info_type_name, sip->name);
 			sip->warpout_accel_exp = 1.0f;
 		}
 	}
@@ -2082,7 +2487,7 @@
 	{
 		stuff_float(&sip->warpout_radius);
 		if(sip->warpout_radius <= 0.0f) {
-			Warning(LOCATION, "Warp-out radius specified as 0 or less on ship '%s'; value ignored", sip->name);
+			Warning(LOCATION, "Warp-out radius specified as 0 or less on %s '%s'; value ignored", info_type_name, sip->name);
 		}
 	}
 
@@ -2126,7 +2531,7 @@
 		stuff_float(&sip->prop_exp_rad_mult);
 		if(sip->prop_exp_rad_mult <= 0) {
 			// on invalid value return to default setting
-			Warning(LOCATION, "Propagating explosion radius multiplier was set to non-positive value.\nDefaulting multiplier to 1.0 on ship '%s'.\n", sip->name);
+			Warning(LOCATION, "Propagating explosion radius multiplier was set to non-positive value.\nDefaulting multiplier to 1.0 on %s '%s'.\n", info_type_name, sip->name);
 			sip->prop_exp_rad_mult = 1.0f;
 		}
 	}
@@ -2492,7 +2897,7 @@
 		stuff_float(&sip->max_hull_strength);
 		if (sip->max_hull_strength < 0.0f)
 		{
-			Warning(LOCATION, "Max hull strength on ship %s cannot be less than 0.  Defaulting to 100.\n", sip->name, sip->max_hull_strength);
+			Warning(LOCATION, "Max hull strength on %s '%s' cannot be less than 0.  Defaulting to 100.\n", info_type_name, sip->name, sip->max_hull_strength);
 			sip->max_hull_strength = 100.0f;
 		}
 	}
@@ -2546,7 +2951,7 @@
 		sip->armor_type_idx = armor_type_get_idx(buf);
 
 		if(sip->armor_type_idx == -1)
-			Warning(LOCATION,"Invalid armor name %s specified for hull in ship class %s", buf, sip->name);
+			Warning(LOCATION,"Invalid armor name %s specified for hull in %s '%s'", buf, info_type_name, sip->name);
 	}
 
 	if(optional_string("$Shield Armor Type:"))
@@ -2555,7 +2960,7 @@
 		sip->shield_armor_type_idx = armor_type_get_idx(buf);
 
 		if(sip->shield_armor_type_idx == -1)
-			Warning(LOCATION,"Invalid armor name %s specified for shield in ship class %s", buf, sip->name);
+			Warning(LOCATION,"Invalid armor name %s specified for shield in %s '%s'", buf, info_type_name, sip->name);
 	}
 
 	if (optional_string("$Flags:"))
@@ -2606,7 +3011,7 @@
 					flag_found = true;
 
 					if (Ship_flags[idx].var == 255)
-						Warning(LOCATION, "Use of '%s' flag for ship '%s' - this flag is no longer needed.", Ship_flags[idx].name, sip->name);
+						Warning(LOCATION, "Use of '%s' flag for %s '%s' - this flag is no longer needed.", Ship_flags[idx].name, info_type_name, sip->name);
 					else if (Ship_flags[idx].var == 0)
 						sip->flags |= Ship_flags[idx].def;
 					else if (Ship_flags[idx].var == 1)
@@ -2628,7 +3033,7 @@
 	// Goober5000 - ensure number of banks checks out
 	if (sip->num_primary_banks > MAX_SHIP_PRIMARY_BANKS)
 	{
-		Error(LOCATION, "Ship Class %s has too many primary banks (%d).  Maximum for ships is currently %d.\n", sip->name, sip->num_primary_banks, MAX_SHIP_PRIMARY_BANKS);
+		Error(LOCATION, "%s '%s' has too many primary banks (%d).  Maximum for ships is currently %d.\n", info_type_name, sip->name, sip->num_primary_banks, MAX_SHIP_PRIMARY_BANKS);
 	}
 
 	// copy to regular allowed_weapons array
@@ -2699,7 +3104,7 @@
 		}
 
 		if (!(sip->afterburner_fuel_capacity) ) {
-			Warning(LOCATION, "Ship class %s has an afterburner but has no afterburner fuel. Setting fuel to 1", sip->name);
+			Warning(LOCATION, "%s '%s' has an afterburner but has no afterburner fuel. Setting fuel to 1", info_type_name, sip->name);
 			sip->afterburner_fuel_capacity = 1.0f;
 		}
 	}
@@ -2734,7 +3139,7 @@
 		}
 
 		if (trails_warning)
-			Warning(LOCATION, "Ship %s entry has $Trails field specified, but no properties given.", sip->name);
+			Warning(LOCATION, "%s %s entry has $Trails field specified, but no properties given.", info_type_name, sip->name);
 	}
 
 	if (optional_string("$Countermeasure type:")) {
@@ -2741,9 +3146,9 @@
 		stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
 		int res = weapon_info_lookup(buf);
 		if (res < 0) {
-			Warning(LOCATION, "Could not find weapon type '%s' to use as countermeasure on ship class '%s'", buf, sip->name);
+			Warning(LOCATION, "Could not find weapon type '%s' to use as countermeasure on %s '%s'", buf, info_type_name, sip->name);
 		} else if (Weapon_info[res].wi_flags & WIF_BEAM) {
-			Warning(LOCATION, "Attempt made to set a beam weapon as a countermeasure on ship class '%s'", sip->name);
+			Warning(LOCATION, "Attempt made to set a beam weapon as a countermeasure on %s '%s'", info_type_name, sip->name);
 		} else {
 			sip->cmeasure_type = res;
 		}
@@ -2838,9 +3243,9 @@
 
 	// check for inconsistencies
 	if ((sip->bii_index_wing_with_cargo >= 0) && (sip->bii_index_wing < 0 || sip->bii_index_ship_with_cargo < 0))
-		Warning(LOCATION, "Ship '%s' has a wing-with-cargo briefing icon but is missing a wing briefing icon or a ship-with-cargo briefing icon!", sip->name);
+		Warning(LOCATION, "%s '%s' has a wing-with-cargo briefing icon but is missing a wing briefing icon or a ship-with-cargo briefing icon!", info_type_name, sip->name);
 	if ((sip->bii_index_wing_with_cargo < 0) && (sip->bii_index_wing >= 0) && (sip->bii_index_ship_with_cargo >= 0))
-		Warning(LOCATION, "Ship '%s' has both a wing briefing icon and a ship-with-cargo briefing icon but does not have a wing-with-cargo briefing icon!", sip->name);
+		Warning(LOCATION, "%s '%s' has both a wing briefing icon and a ship-with-cargo briefing icon but does not have a wing-with-cargo briefing icon!", info_type_name, sip->name);
 
 	if ( optional_string("$Score:") ){
 		stuff_int( &sip->score );
@@ -2974,7 +3379,7 @@
 		else if ( optional_string("$Afterburner Particle Bitmap:") )
 			afterburner = true;
 		else
-			Error( LOCATION, "formatting error in the thruster's particle section for ship %s\n", sip->name );
+			Error( LOCATION, "formatting error in the thruster's particle section for %s '%s'\n", info_type_name, sip->name );
 
 		generic_anim_init(&tpart.thruster_bitmap, NULL);
 		stuff_string(tpart.thruster_bitmap.filename, F_NAME, MAX_FILENAME_LEN);
@@ -3007,7 +3412,7 @@
 	}
 	
 	else if ( optional_string("$Stealth") ) {
-		Warning(LOCATION, "Ship %s is missing the colon after \"$Stealth\". Note that you may also use the ship flag \"stealth\".", sip->name);
+		Warning(LOCATION, "%s '%s' is missing the colon after \"$Stealth\". Note that you may also use the ship flag \"stealth\".", info_type_name, sip->name);
 		sip->flags |= SIF_STEALTH;
 	}
 
@@ -3029,7 +3434,7 @@
 
 		// this means you've reached the max # of contrails for a ship
 		if (sip->ct_count >= MAX_SHIP_CONTRAILS) {
-			Warning(LOCATION, "%s has more contrails than the max of %d", sip->name, MAX_SHIP_CONTRAILS);
+			Warning(LOCATION, "%s '%s' has more contrails than the max of %d", info_type_name, sip->name, MAX_SHIP_CONTRAILS);
 			break;
 		}
 
@@ -3081,11 +3486,11 @@
 			if(sip->num_maneuvering < MAX_MAN_THRUSTERS) {
 				mtp = &sip->maneuvering[sip->num_maneuvering++];
 			} else {
-				Warning(LOCATION, "Too many maneuvering thrusters on ship '%s'; maximum is %d", sip->name, MAX_MAN_THRUSTERS);
+				Warning(LOCATION, "Too many maneuvering thrusters on %s '%s'; maximum is %d", info_type_name, sip->name, MAX_MAN_THRUSTERS);
 			}
 		} else {
 			mtp = &manwich;
-			Warning(LOCATION, "Invalid index (%d) specified for maneuvering thruster on ship %s", idx, sip->name);
+			Warning(LOCATION, "Invalid index (%d) specified for maneuvering thruster on %s '%s'", idx, info_type_name, sip->name);
 		}
 
 		if(optional_string("+Used for:")) {
@@ -3167,10 +3572,10 @@
 		iff_data[1] = iff_lookup(iff_2);
 
 		if (iff_data[0] == -1)
-			WarningEx(LOCATION, "Ship %s\nIFF colour seen by \"%s\" invalid!", sip->name, iff_1);
+			WarningEx(LOCATION, "%s '%s'\nIFF colour seen by \"%s\" invalid!", info_type_name, sip->name, iff_1);
 
 		if (iff_data[1] == -1)
-			WarningEx(LOCATION, "Ship %s\nIFF colour when IFF is \"%s\" invalid!", sip->name, iff_2);
+			WarningEx(LOCATION, "%s '%s'\nIFF colour when IFF is \"%s\" invalid!", info_type_name, sip->name, iff_2);
 
 		// Set the color
 		required_string("+As Color:");
@@ -3210,7 +3615,7 @@
 				}
 			}
 			if (i == num_groups) {
-				Warning(LOCATION,"Unidentified priority group '%s' set for ship class '%s'\n", target_group_strings[j].c_str(), sip->name);
+				Warning(LOCATION,"Unidentified priority group '%s' set for %s '%s'\n", target_group_strings[j].c_str(), info_type_name, sip->name);
 			}
 		}
 	}
@@ -3259,7 +3664,7 @@
 	}
 
 	while (cont_flag) {
-		int r = required_string_3("#End", "$Subsystem:", "$Name" );
+		int r = required_string_one_of(3, "#End", "$Subsystem:", type_name);
 		switch (r) {
 		case 0:
 			cont_flag = 0;
@@ -3285,7 +3690,7 @@
 			{
 				if( sip->n_subsystems + n_subsystems >= MAX_MODEL_SUBSYSTEMS )
 				{
-					Warning(LOCATION, "Number of subsystems for ship entry '%s' (%d) exceeds max of %d; only the first %d will be used", sip->name, sip->n_subsystems, n_subsystems, MAX_MODEL_SUBSYSTEMS);
+					Warning(LOCATION, "Number of subsystems for %s '%s' (%d) exceeds max of %d; only the first %d will be used", info_type_name, sip->name, sip->n_subsystems, n_subsystems, MAX_MODEL_SUBSYSTEMS);
 					break;
 				}
 				sp = &subsystems[n_subsystems++];			// subsystems a local -- when done, we will malloc and copy
@@ -3364,9 +3769,9 @@
 				}
 				else
 				{
-					Error(LOCATION, "Malformed $Subsystem entry '%s' %s.\n\n"
+					Error(LOCATION, "Malformed $Subsystem entry '%s' in %s '%s'.\n\n"
 						"Specify a turning rate or remove the trailing comma.",
-						sp->subobj_name, sip->name);
+						sp->subobj_name, info_type_name, sip->name);
 				}
 			}
 
@@ -3385,7 +3790,7 @@
 				sp->armor_type_idx = armor_type_get_idx(buf);
 
 				if (sp->armor_type_idx == -1)
-					WarningEx(LOCATION, "Ship %s, subsystem %s\nInvalid armor type %s!", sip->name, sp->subobj_name, buf);
+					WarningEx(LOCATION, "%s '%s', subsystem %s\nInvalid armor type %s!", info_type_name, sip->name, sp->subobj_name, buf);
 			}
 
 			//	Get primary bank weapons
@@ -3401,7 +3806,7 @@
 				sp->engine_wash_pointer = get_engine_wash_pointer(name_tmp);
 
 				if(sp->engine_wash_pointer == NULL)
-					WarningEx(LOCATION,"Invalid engine wash name %s specified for subsystem %s in ship class %s", name_tmp, sp->subobj_name, sip->name);
+					WarningEx(LOCATION,"Invalid engine wash name %s specified for subsystem %s in %s '%s'", name_tmp, sp->subobj_name, info_type_name, sip->name);
 			}
 
 			parse_sound("$AliveSnd:", &sp->alive_snd, sp->subobj_name);
@@ -3478,7 +3883,7 @@
 						}
 					}
 					if (j == num_groups) {
-						Warning(LOCATION, "Unidentified target priority '%s' set for\nsubsystem '%s' in ship class '%s'.", tgt_priorities[i].c_str(), sp->subobj_name, sip->name);
+						Warning(LOCATION, "Unidentified target priority '%s' set for\nsubsystem '%s' in %s '%s'.", tgt_priorities[i].c_str(), sp->subobj_name, info_type_name, sip->name);
 					}
 				}
 			}
@@ -3501,13 +3906,13 @@
 						stuff_float(&tempf);
 
 						if (tempf < 0) {
-							mprintf(("RoF multiplier clamped to 0 for subsystem '%s' in ship class '%s'.\n", sp->subobj_name, sip->name));
+							mprintf(("RoF multiplier clamped to 0 for subsystem '%s' in %s '%s'.\n", sp->subobj_name, info_type_name, sip->name));
 							sp->turret_rof_scaler = 0;
 						} else {
 							sp->turret_rof_scaler = tempf;
 						}
 					} else {
-						Warning(LOCATION, "RoF multiplier not set for subsystem\n'%s' in ship class '%s'.", sp->subobj_name, sip->name);
+						Warning(LOCATION, "RoF multiplier not set for subsystem\n'%s' in %s '%s'.", sp->subobj_name, info_type_name, sip->name);
 					}
 				}
 			}
@@ -3578,17 +3983,17 @@
 			}
 
 			if ((sp->flags & MSS_FLAG_TURRET_FIXED_FP) && !(sp->flags & MSS_FLAG_USE_MULTIPLE_GUNS)) {
-				Warning(LOCATION, "\"fixed firingpoints\" flag used without \"use multiple guns\" flag on a subsystem on ship %s.\n\"use multiple guns\" flags added by default\n", sip->name);
+				Warning(LOCATION, "\"fixed firingpoints\" flag used without \"use multiple guns\" flag on a subsystem on %s '%s'.\n\"use multiple guns\" flags added by default\n", info_type_name, sip->name);
 				sp->flags |= MSS_FLAG_USE_MULTIPLE_GUNS;
 			}
 
 			if (old_flags) {
 				mprintf(("Use of deprecated subsystem syntax.  Please use the $Flags: field for subsystem flags.\n\n" \
-				"At least one of the following tags was used on ship %s, subsystem %s:\n" \
+				"At least one of the following tags was used on %s '%s', subsystem %s:\n" \
 				"\t+untargetable\n" \
 				"\t+carry-no-damage\n" \
 				"\t+use-multiple-guns\n" \
-				"\t+fire-down-normals\n", sip->name, sp->subobj_name));
+				"\t+fire-down-normals\n", info_type_name, sip->name, sp->subobj_name));
 			}
 
 			while(optional_string("$animation:"))
@@ -3757,8 +4162,11 @@
 		case 2:
 			cont_flag = 0;
 			break;
+		case -1:
+			// Possible if -noparseerrors is used.
+			break;
 		default:
-			Int3();	// Impossible return value from required_string_3.
+			Assertion(false, "This should never happen.\n");	// Impossible return value from required_string_one_of().
 		}
 	}	
 
@@ -4158,6 +4566,17 @@
 		required_string("#End");
 	}
 
+	if ( optional_string("#Ship Templates") ) {
+
+		while ( required_string_either("#End", "$Template:") ) {
+			if ( parse_ship_template() ) {
+				continue;
+			}
+		}
+
+		required_string("#End");
+	}
+
 	//Add ship classes
 	if(optional_string("#Ship Classes"))
 	{
@@ -4276,6 +4695,25 @@
 			}
 		}
 	}
+
+	// Clear out ship templates, since they're no longer needed. -MageKing17
+	for ( SCP_vector<ship_info>::iterator it = Ship_templates.begin(); it != Ship_templates.end(); ++it ) {
+		// free memory alloced for subsystem storage
+		if ( it->subsystems != NULL ) {
+			for(int n = 0; n < it->n_subsystems; n++) {
+				if (it->subsystems[n].triggers != NULL) {
+					vm_free(it->subsystems[n].triggers);
+					it->subsystems[n].triggers = NULL;
+				}
+			}
+
+			vm_free(it->subsystems);
+			it->subsystems = NULL;
+		}
+
+		it->free_strings();
+	}
+	Ship_templates.clear();
 }
 
 /**
@@ -11886,6 +12324,21 @@
 	return -1;
 }
 
+/**
+ * Return the index of Ship_templates[].name that is *token.
+ */
+int ship_template_lookup(const char *token)
+{
+	int	i;
+
+	for ( i = 0; i < (int)Ship_templates.size(); i++ ) {
+		if ( !stricmp(token, Ship_templates[i].name) ) {
+			return i;
+		}
+	}
+	return -1;
+}
+
 // Goober5000
 int ship_info_lookup(const char *token)
 {
@@ -13285,8 +13738,9 @@
 	// free this too! -- Goober5000
 	ship_clear_subsystems();
 
-	// free memory alloced for subsystem storage
+	// free info from parsed table data
 	for ( i = 0; i < Num_ship_classes; i++ ) {
+		// free memory alloced for subsystem storage
 		if ( Ship_info[i].subsystems != NULL ) {
 			for(n = 0; n < Ship_info[i].n_subsystems; n++) {
 				if (Ship_info[i].subsystems[n].triggers != NULL) {
@@ -13299,105 +13753,9 @@
 			Ship_info[i].subsystems = NULL;
 		}
 
-		// free info from parsed table data
-		if (Ship_info[i].type_str != NULL) {
-			vm_free(Ship_info[i].type_str);
-			Ship_info[i].type_str = NULL;
-		}
-
-		if (Ship_info[i].maneuverability_str != NULL) {
-			vm_free(Ship_info[i].maneuverability_str);
-			Ship_info[i].maneuverability_str = NULL;
-		}
-
-		if (Ship_info[i].armor_str != NULL) {
-			vm_free(Ship_info[i].armor_str);
-			Ship_info[i].armor_str = NULL;
-		}
-
-		if (Ship_info[i].manufacturer_str != NULL) {
-			vm_free(Ship_info[i].manufacturer_str);
-			Ship_info[i].manufacturer_str = NULL;
-		}
-
-		if (Ship_info[i].desc != NULL) {
-			vm_free(Ship_info[i].desc);
-			Ship_info[i].desc = NULL;
-		}
-
-		if (Ship_info[i].tech_desc != NULL) {
-			vm_free(Ship_info[i].tech_desc);
-			Ship_info[i].tech_desc = NULL;
-		}
-
-		if (Ship_info[i].ship_length != NULL) {
-			vm_free(Ship_info[i].ship_length);
-			Ship_info[i].ship_length = NULL;
-		}
-
-		if (Ship_info[i].gun_mounts != NULL) {
-			vm_free(Ship_info[i].gun_mounts);
-			Ship_info[i].gun_mounts = NULL;
-		}
-
-		if (Ship_info[i].missile_banks != NULL) {
-			vm_free(Ship_info[i].missile_banks);
-			Ship_info[i].missile_banks = NULL;
-		}
+		Ship_info[i].free_strings();
 	}
 
-	// free info from parsed table data
-	for (i=0; i<MAX_SHIP_CLASSES; i++) {
-		if ( Ship_info[i].subsystems != NULL ) {
-			for(n = 0; n < Ship_info[i].n_subsystems; n++) {
-				if (Ship_info[i].subsystems[n].triggers != NULL) {
-					vm_free(Ship_info[i].subsystems[n].triggers);
-					Ship_info[i].subsystems[n].triggers = NULL;
-				}
-			}
-			
-			vm_free(Ship_info[i].subsystems);
-			Ship_info[i].subsystems = NULL;
-		}
-		
-		if(Ship_info[i].type_str != NULL){
-			vm_free(Ship_info[i].type_str);
-			Ship_info[i].type_str = NULL;
-		}
-		if(Ship_info[i].maneuverability_str != NULL){
-			vm_free(Ship_info[i].maneuverability_str);
-			Ship_info[i].maneuverability_str = NULL;
-		}
-		if(Ship_info[i].armor_str != NULL){
-			vm_free(Ship_info[i].armor_str);
-			Ship_info[i].armor_str = NULL;
-		}
-		if(Ship_info[i].manufacturer_str != NULL){
-			vm_free(Ship_info[i].manufacturer_str);
-			Ship_info[i].manufacturer_str = NULL;
-		}
-		if(Ship_info[i].desc != NULL){
-			vm_free(Ship_info[i].desc);
-			Ship_info[i].desc = NULL;
-		}
-		if(Ship_info[i].tech_desc != NULL){
-			vm_free(Ship_info[i].tech_desc);
-			Ship_info[i].tech_desc = NULL;
-		}
-		if(Ship_info[i].ship_length != NULL){
-			vm_free(Ship_info[i].ship_length);
-			Ship_info[i].ship_length = NULL;
-		}
-		if(Ship_info[i].gun_mounts != NULL){
-			vm_free(Ship_info[i].gun_mounts);
-			Ship_info[i].gun_mounts = NULL;
-		}
-		if(Ship_info[i].missile_banks != NULL){
-			vm_free(Ship_info[i].missile_banks);
-			Ship_info[i].missile_banks = NULL;
-		}
-	}
-
 	for (i = 0; i < (int)Ship_types.size(); i++) {
 		Ship_types[i].ai_actively_pursues.clear();
 		Ship_types[i].ai_actively_pursues_temp.clear();
Index: code/ship/ship.h
===================================================================
--- code/ship/ship.h	(revision 11204)
+++ code/ship/ship.h	(working copy)
@@ -1431,6 +1431,11 @@
 	SCP_vector<cockpit_display_info> displays;
 
 	SCP_map<SCP_string, path_metadata> pathMetadata;
+
+	const ship_info &operator=(const ship_info& other);
+	void free_strings();
+private:
+	void clone(const ship_info& other);
 };
 
 extern int Num_wings;
@@ -1620,7 +1625,8 @@
 //	Stuff vector *pos with absolute position.
 extern int get_subsystem_pos(vec3d *pos, object *objp, ship_subsys *subsysp);
 
-int parse_ship_values(ship_info* sip, bool first_time, bool replace);
+int parse_ship_values(ship_info* sip, const bool is_template, const bool first_time, const bool replace);
+int ship_template_lookup(const char *name = NULL);
 void parse_ship_particle_effect(ship_info* sip, particle_effect* pe, char *id_string);
 
 extern int ship_info_lookup(const char *name = NULL);
