/*
 * Copyright (C) Volition, Inc. 1999.  All rights reserved.
 *
 * All source code herein is the property of Volition, Inc. You may not sell 
 * or otherwise commercially exploit the source or things you created based on the 
 * source.
 *
*/



#include "ai/aibig.h"
#include "asteroid/asteroid.h"
#include "cmdline/cmdline.h"
#include "cmeasure/cmeasure.h"
#include "debugconsole/console.h"
#include "fireball/fireballs.h"
#include "freespace2/freespace.h"
#include "gamesnd/gamesnd.h"
#include "globalincs/linklist.h"
#include "graphics/grbatch.h"
#include "hud/hud.h"
#include "hud/hudartillery.h"
#include "iff_defs/iff_defs.h"
#include "io/joy_ff.h"
#include "io/timer.h"
#include "localization/localize.h"
#include "math/staticrand.h"
#include "mod_table/mod_table.h"
#include "model/modelrender.h"
#include "network/multi.h"
#include "network/multimsgs.h"
#include "network/multiutil.h"
#include "object/objcollide.h"
#include "object/object.h"
#include "parse/parselo.h"
#include "parse/scripting.h"
#include "particle/particle.h"
#include "playerman/player.h"
#include "radar/radar.h"
#include "radar/radarsetup.h"
#include "render/3d.h"
#include "ship/ship.h"
#include "ship/shiphit.h"
#include "stats/scoring.h"
#include "weapon/beam.h"	// for BEAM_TYPE_? definitions
#include "weapon/corkscrew.h"
#include "weapon/emp.h"
#include "weapon/flak.h"
#include "weapon/muzzleflash.h"
#include "weapon/swarm.h"
#include "weapon/weapon.h"

// Since SSMs are parsed after weapons, if we want to allow SSM strikes to be specified by name, we need to store those names until after SSMs are parsed.
typedef struct delayed_ssm_data {
	SCP_string filename;
	int linenum;
	SCP_string ssm_entry;
} delayed_ssm_data;
SCP_map<SCP_string, delayed_ssm_data> Delayed_SSM_data;
SCP_vector<SCP_string> Delayed_SSM_names;

typedef struct delayed_ssm_index_data {
	SCP_string filename;
	int linenum;
} delayed_ssm_index_data;
SCP_map<SCP_string, delayed_ssm_index_data> Delayed_SSM_indices_data;
SCP_vector<SCP_string> Delayed_SSM_indices;


#ifndef NDEBUG
int Weapon_flyby_sound_enabled = 1;
DCF_BOOL( weapon_flyby, Weapon_flyby_sound_enabled )
#endif

static int Weapon_flyby_sound_timer;	

weapon Weapons[MAX_WEAPONS];
weapon_info Weapon_info[MAX_WEAPON_TYPES];

#define		MISSILE_OBJ_USED	(1<<0)			// flag used in missile_obj struct
#define		MAX_MISSILE_OBJS	MAX_WEAPONS		// max number of missiles tracked in missile list
missile_obj Missile_objs[MAX_MISSILE_OBJS];	// array used to store missile object indexes
missile_obj Missile_obj_list;						// head of linked list of missile_obj structs

//WEAPON SUBTYPE STUFF
char *Weapon_subtype_names[] = {
	"Laser",
	"Missile",
	"Beam"
};
int Num_weapon_subtypes = sizeof(Weapon_subtype_names)/sizeof(char *);

flag_def_list Burst_fire_flags[] = {
	{ "fast firing",		WBF_FAST_FIRING,		0 },
	{ "random length",		WBF_RANDOM_LENGTH,		0 }
};

int Num_burst_fire_flags = sizeof(Burst_fire_flags)/sizeof(flag_def_list);

weapon_explosions Weapon_explosions;

SCP_vector<lod_checker> LOD_checker;

int Num_weapon_types = 0;

int Num_weapons = 0;
int Weapons_inited = 0;
int Weapon_expl_initted = 0;

int laser_model_inner = -1;
int laser_model_outer = -1;

int missile_model = -1;

int     First_secondary_index = -1;
int		Default_cmeasure_index = -1;

static int *used_weapons = NULL;

int	Num_spawn_types = 0;
char **Spawn_names = NULL;

int Num_player_weapon_precedence;				// Number of weapon types in Player_weapon_precedence
int Player_weapon_precedence[MAX_WEAPON_TYPES];	// Array of weapon types, precedence list for player weapon selection

// Used to avoid playing too many impact sounds in too short a time interval.
// This will elimate the odd "stereo" effect that occurs when two weapons impact at 
// nearly the same time, like from a double laser (also saves sound channels!)
#define	IMPACT_SOUND_DELTA	50		// in milliseconds
int		Weapon_impact_timer;			// timer, initialized at start of each mission

// energy suck defines
#define ESUCK_DEFAULT_WEAPON_REDUCE				(10.0f)
#define ESUCK_DEFAULT_AFTERBURNER_REDUCE		(10.0f)

// scale factor for supercaps taking damage from weapons which are not "supercap" weapons
#define SUPERCAP_DAMAGE_SCALE			0.25f

// scale factor for big ships getting hit by flak
#define FLAK_DAMAGE_SCALE				0.05f

//default time of a homing weapon to not home
#define HOMING_DEFAULT_FREE_FLIGHT_TIME	0.5f

// time delay between each swarm missile that is fired
#define SWARM_MISSILE_DELAY				150

// homing missiles have an extended lifetime so they don't appear to run out of gas before they can hit a moving target at extreme
// range. Check the comment in weapon_set_tracking_info() for more details
#define LOCKED_HOMING_EXTENDED_LIFE_FACTOR			1.2f

extern int compute_num_homing_objects(object *target_objp);

extern void fs2netd_add_table_validation(const char *tblname);


weapon_explosions::weapon_explosions()
{
	ExplosionInfo.clear();
}

int weapon_explosions::GetIndex(char *filename)
{
	if ( filename == NULL ) {
		Int3();
		return -1;
	}

	for (size_t i = 0; i < ExplosionInfo.size(); i++) {
		if ( !stricmp(ExplosionInfo[i].lod[0].filename, filename)) {
			return (int)i;
		}
	}

	return -1;
}

int weapon_explosions::Load(char *filename, int expected_lods)
{
	char name_tmp[MAX_FILENAME_LEN] = "";
	int bitmap_id = -1;
	int nframes, nfps;
	weapon_expl_info new_wei;

	Assert( expected_lods <= MAX_WEAPON_EXPL_LOD );

	//Check if it exists
	int idx = GetIndex(filename);

	if (idx != -1)
		return idx;

	new_wei.lod_count = 1;

	strcpy_s(new_wei.lod[0].filename, filename);
	new_wei.lod[0].bitmap_id = bm_load_animation(filename, &new_wei.lod[0].num_frames, &new_wei.lod[0].fps, NULL, 1);

	if (new_wei.lod[0].bitmap_id < 0) {
		Warning(LOCATION, "Weapon explosion '%s' does not have an LOD0 anim!", filename);

		// if we don't have the first then it's only safe to assume that the rest are missing or not usable
		return -1;
	}

	// 2 chars for the lod, 4 for the extension that gets added automatically
	if ( (MAX_FILENAME_LEN - strlen(filename)) > 6 ) {
		for (idx = 1; idx < expected_lods; idx++) {
			sprintf(name_tmp, "%s_%d", filename, idx);

			bitmap_id = bm_load_animation(name_tmp, &nframes, &nfps, NULL, 1);

			if (bitmap_id > 0) {
				strcpy_s(new_wei.lod[idx].filename, name_tmp);
				new_wei.lod[idx].bitmap_id = bitmap_id;
				new_wei.lod[idx].num_frames = nframes;
				new_wei.lod[idx].fps = nfps;

				new_wei.lod_count++;
			} else {
				break;
			}
		}

		if (new_wei.lod_count != expected_lods)
			Warning(LOCATION, "For '%s', %i of %i LODs are missing!", filename, expected_lods - new_wei.lod_count, expected_lods);
	}
	else {
		Warning(LOCATION, "Filename '%s' is too long to have any LODs.", filename);
	}

	ExplosionInfo.push_back( new_wei );

	return (int)(ExplosionInfo.size() - 1);
}

void weapon_explosions::PageIn(int idx)
{
	int i;

	if ( (idx < 0) || (idx >= (int)ExplosionInfo.size()) )
		return;

	weapon_expl_info *wei = &ExplosionInfo[idx];

	for ( i = 0; i < wei->lod_count; i++ ) {
		if ( wei->lod[i].bitmap_id >= 0 ) {
			bm_page_in_xparent_texture( wei->lod[i].bitmap_id, wei->lod[i].num_frames );
		}
	}
}

int weapon_explosions::GetAnim(int weapon_expl_index, vec3d *pos, float size)
{
	if ( (weapon_expl_index < 0) || (weapon_expl_index >= (int)ExplosionInfo.size()) )
		return -1;

	//Get our weapon expl for the day
	weapon_expl_info *wei = &ExplosionInfo[weapon_expl_index];

	if (wei->lod_count == 1)
		return wei->lod[0].bitmap_id;

	// now we have to do some work
	vertex v;
	int x, y, w, h, bm_size;
	int must_stop = 0;
	int best_lod = 1;
	int behind = 0;

	// start the frame
	extern int G3_count;

	if(!G3_count){
		g3_start_frame(1);
		must_stop = 1;
	}
	g3_set_view_matrix(&Eye_position, &Eye_matrix, Eye_fov);

	// get extents of the rotated bitmap
	g3_rotate_vertex(&v, pos);

	// if vertex is behind, find size if in front, then drop down 1 LOD
	if (v.codes & CC_BEHIND) {
		float dist = vm_vec_dist_quick(&Eye_position, pos);
		vec3d temp;

		behind = 1;
		vm_vec_scale_add(&temp, &Eye_position, &Eye_matrix.vec.fvec, dist);
		g3_rotate_vertex(&v, &temp);

		// if still behind, bail and go with default
		if (v.codes & CC_BEHIND) {
			behind = 0;
		}
	}

	if (!g3_get_bitmap_dims(wei->lod[0].bitmap_id, &v, size, &x, &y, &w, &h, &bm_size)) {
		if (Detail.hardware_textures == 4) {
			// straight LOD
			if(w <= bm_size/8){
				best_lod = 3;
			} else if(w <= bm_size/2){
				best_lod = 2;
			} else if(w <= 1.3f*bm_size){
				best_lod = 1;
			} else {
				best_lod = 0;
			}
		} else {
			// less aggressive LOD for lower detail settings
			if(w <= bm_size/8){
				best_lod = 3;
			} else if(w <= bm_size/3){
				best_lod = 2;
			} else if(w <= (1.15f*bm_size)){
				best_lod = 1;
			} else {
				best_lod = 0;
			}		
		}
	}

	// if it's behind, bump up LOD by 1
	if (behind)
		best_lod++;

	// end the frame
	if (must_stop)
		g3_end_frame();

	best_lod = MIN(best_lod, wei->lod_count - 1);
	Assert( (best_lod >= 0) && (best_lod < MAX_WEAPON_EXPL_LOD) );

	return wei->lod[best_lod].bitmap_id;
}


void parse_weapon_expl_tbl(const char *filename)
{
	uint i;
	lod_checker lod_check;
	
	try
	{
		read_file_text(filename, CF_TYPE_TABLES);
		reset_parse();

		required_string("#Start");
		while (required_string_either("#End", "$Name:"))
		{
			memset(&lod_check, 0, sizeof(lod_checker));

			// base filename
			required_string("$Name:");
			stuff_string(lod_check.filename, F_NAME, MAX_FILENAME_LEN);

			//Do we have an LOD num
			if (optional_string("$LOD:"))
			{
				stuff_int(&lod_check.num_lods);
			}

			// only bother with this if we have 1 or more lods and less than max lods,
			// otherwise the stardard level loading will take care of the different effects
			if ((lod_check.num_lods > 0) || (lod_check.num_lods < MAX_WEAPON_EXPL_LOD)) {
				// name check, update lod count if it already exists
				for (i = 0; i < LOD_checker.size(); i++) {
					if (!stricmp(LOD_checker[i].filename, lod_check.filename)) {
						LOD_checker[i].num_lods = lod_check.num_lods;
					}
				}

				// old entry not found, add new entry
				if (i == LOD_checker.size()) {
					LOD_checker.push_back(lod_check);
				}
			}
		}
		required_string("#End");
	}
	catch (const parse::ParseException& e)
	{
		mprintf(("TABLES: Unable to parse '%s'!  Error message = %s.\n", filename, e.what()));
		return;
	}
}

/**
 * Clear out the Missile_obj_list
 */
void missile_obj_list_init()
{
	int i;

	list_init(&Missile_obj_list);
	for ( i = 0; i < MAX_MISSILE_OBJS; i++ ) {
		Missile_objs[i].flags = 0;
	}
}

/**
 * Add a node from the Missile_obj_list.
 * @note Only called from weapon_create()
 */
int missile_obj_list_add(int objnum)
{
	int i;

	for ( i = 0; i < MAX_MISSILE_OBJS; i++ ) {
		if ( !(Missile_objs[i].flags & MISSILE_OBJ_USED) )
			break;
	}
	if ( i == MAX_MISSILE_OBJS ) {
		Error(LOCATION, "Fatal Error: Ran out of missile object nodes\n");
		return -1;
	}
	
	Missile_objs[i].flags = 0;
	Missile_objs[i].objnum = objnum;
	list_append(&Missile_obj_list, &Missile_objs[i]);
	Missile_objs[i].flags |= MISSILE_OBJ_USED;

	return i;
}

/**
 * Remove a node from the Missile_obj_list.
 * @note Only called from weapon_delete()
 */
void missle_obj_list_remove(int index)
{
	Assert(index >= 0 && index < MAX_MISSILE_OBJS);
	list_remove(&Missile_obj_list, &Missile_objs[index]);	
	Missile_objs[index].flags = 0;
}

/**
 * Called by the save/restore code to rebuild Missile_obj_list
 */
void missile_obj_list_rebuild()
{
	object *objp;

	missile_obj_list_init();

	for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
		if ( objp->type == OBJ_WEAPON && Weapon_info[Weapons[objp->instance].weapon_info_index].subtype == WP_MISSILE ) {
			Weapons[objp->instance].missile_list_index = missile_obj_list_add(OBJ_INDEX(objp));
		}
	}
}

/**
 * Called externally to generate an address from an index into
 * the Missile_objs[] array
 */
missile_obj *missile_obj_return_address(int index)
{
	Assert(index >= 0 && index < MAX_MISSILE_OBJS);
	return &Missile_objs[index];
}

/**
 * Return the index of Weapon_info[].name that is *name.
 */
int weapon_info_lookup(const char *name)
{
	// bogus
	if (name == NULL)
		return -1;

	for (int i=0; i<Num_weapon_types; i++)
		if (!stricmp(name, Weapon_info[i].name))
			return i;

	return -1;
}

#define DEFAULT_WEAPON_SPAWN_COUNT	10

//	Parse the weapon flags.
void parse_wi_flags(weapon_info *weaponp, int wi_flags, int wi_flags2, int wi_flags3)
{
	const char *spawn_str = NOX("Spawn");
	const size_t spawn_str_len = strlen(spawn_str);

	//Make sure we HAVE flags :p
	if(!optional_string("$Flags:"))
		return;

	char	weapon_strings[MAX_WEAPON_FLAGS][NAME_LENGTH];
	int	num_strings;

	num_strings = stuff_string_list(weapon_strings, MAX_WEAPON_FLAGS);

	if (optional_string("+override")) {
		// reseting the flag values if set to override the existing flags
		weaponp->wi_flags = wi_flags;
		weaponp->wi_flags2 = wi_flags2;
		weaponp->wi_flags3 = wi_flags3;
	}

	bool set_pierce = false;
	bool set_nopierce = false;
	
	for (int i=0; i<num_strings; i++) {
		if (!strnicmp(spawn_str, weapon_strings[i], 5))
		{
            if (weaponp->num_spawn_weapons_defined < MAX_SPAWN_TYPES_PER_WEAPON)
			{
				//We need more spawning slots
				//allocate in slots of 10
				if((Num_spawn_types % 10) == 0) {
					Spawn_names = (char **)vm_realloc(Spawn_names, (Num_spawn_types + 10) * sizeof(*Spawn_names));
				}

				int	skip_length, name_length;
				char	*temp_string;

				temp_string = weapon_strings[i];

				weaponp->wi_flags |= WIF_SPAWN;
				weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_type = (short)Num_spawn_types;
				skip_length = spawn_str_len + strspn(&temp_string[spawn_str_len], NOX(" \t"));
				char *num_start = strchr(&temp_string[skip_length], ',');
				if (num_start == NULL) {
					weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_count = DEFAULT_WEAPON_SPAWN_COUNT;
					name_length = 999;
				} else {
					weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_count = (short)atoi(num_start+1);
					name_length = num_start - temp_string - skip_length;
				}

                weaponp->total_children_spawned += weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_count;

				Spawn_names[Num_spawn_types] = vm_strndup( &weapon_strings[i][skip_length], name_length );
				Num_spawn_types++;
                weaponp->num_spawn_weapons_defined++;
			} else {
				Warning(LOCATION, "Illegal to have more than %d spawn types for one weapon.\nIgnoring weapon %s", MAX_SPAWN_TYPES_PER_WEAPON, weaponp->name);
			}
		} else if (!stricmp(NOX("Remote Detonate"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_REMOTE;
		else if (!stricmp(NOX("Puncture"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_PUNCTURE;		
		else if (!stricmp(NOX("Big Ship"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_BIG_ONLY;
		else if (!stricmp(NOX("Huge"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_HUGE;
		else if (!stricmp(NOX("Bomber+"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_BOMBER_PLUS;
		else if (!stricmp(NOX("child"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_CHILD;
		else if (!stricmp(NOX("Bomb"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_BOMB;
		else if (!stricmp(NOX("No Dumbfire"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_NO_DUMBFIRE;
		else if (!stricmp(NOX("In tech database"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_IN_TECH_DATABASE;
		else if (!stricmp(NOX("Player allowed"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_PLAYER_ALLOWED;		
		else if (!stricmp(NOX("Particle Spew"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_PARTICLE_SPEW;
		else if (!stricmp(NOX("EMP"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_EMP;
		else if (!stricmp(NOX("Esuck"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_ENERGY_SUCK;
		else if (!stricmp(NOX("Flak"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_FLAK;
		else if (!stricmp(NOX("Corkscrew"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_CORKSCREW;
		else if (!stricmp(NOX("Shudder"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_SHUDDER;
		else if (!stricmp(NOX("Electronics"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_ELECTRONICS;
		else if (!stricmp(NOX("lockarm"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_LOCKARM;		
		else if (!stricmp(NOX("beam"), weapon_strings[i]))
		{
			weaponp->wi_flags |= WIF_BEAM;

			// IMPORTANT: beams pierce shields by default :rolleyes: :p - Goober5000
			weaponp->wi_flags2 |= WIF2_PIERCE_SHIELDS;
		}
		else if (!stricmp(NOX("stream"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_STREAM;
		else if (!stricmp(NOX("supercap"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_SUPERCAP;
		else if (!stricmp(NOX("countermeasure"), weapon_strings[i]))
			weaponp->wi_flags |= WIF_CMEASURE;
		else if (!stricmp(NOX("ballistic"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_BALLISTIC;
		else if (!stricmp(NOX("pierce shields"), weapon_strings[i]))
			set_pierce = true;
		else if (!stricmp(NOX("no pierce shields"), weapon_strings[i]))	// only for beams
			set_nopierce = true;
		else if (!stricmp(NOX("local ssm"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_LOCAL_SSM;
		else if (!stricmp(NOX("tagged only"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_TAGGED_ONLY;
		else if (!stricmp(NOX("beam no whack"), weapon_strings[i]))
		{
			Warning(LOCATION, "The \"beam no whack\" flag has been deprecated.  Set the beam's mass to 0 instead.  This has been done for you.\n");
			weaponp->mass = 0.0f;
		}
		else if (!stricmp(NOX("cycle"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_CYCLE;
		else if (!stricmp(NOX("small only"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_SMALL_ONLY;
		else if (!stricmp(NOX("same turret cooldown"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_SAME_TURRET_COOLDOWN;
		else if (!stricmp(NOX("apply no light"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_MR_NO_LIGHTING;
		else if (!stricmp(NOX("training"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_TRAINING;
		else if (!stricmp(NOX("smart spawn"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_SMART_SPAWN;
		else if (!stricmp(NOX("inherit parent target"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_INHERIT_PARENT_TARGET;
		else if (!stricmp(NOX("no emp kill"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_NO_EMP_KILL;
		else if (!stricmp(NOX("untargeted heat seeker"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_UNTARGETED_HEAT_SEEKER;
		else if (!stricmp(NOX("no radius doubling"), weapon_strings[i])) {
			if (weaponp->wi_flags & WIF_BOMB) {	
				weaponp->wi_flags2 |= WIF2_HARD_TARGET_BOMB;
			}
			else {
				Warning(LOCATION, "Weapon %s is not a bomb but has \"no radius doubling\" set. Ignoring this flag", weaponp->name);
			}
		}
		else if (!stricmp(NOX("no subsystem homing"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_NON_SUBSYS_HOMING;
		else if (!stricmp(NOX("no lifeleft penalty"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_NO_LIFE_LOST_IF_MISSED;
		else if (!stricmp(NOX("can be targeted"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_CAN_BE_TARGETED;
		else if (!stricmp(NOX("show on radar"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_SHOWN_ON_RADAR;
		else if (!stricmp(NOX("show friendly on radar"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_SHOW_FRIENDLY;
		else if (!stricmp(NOX("capital+"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_CAPITAL_PLUS;
		else if (!stricmp(NOX("chain external model fps"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_EXTERNAL_WEAPON_FP;
		else if (!stricmp(NOX("external model launcher"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_EXTERNAL_WEAPON_LNCH;
		else if (!stricmp(NOX("takes blast damage"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_TAKES_BLAST_DAMAGE;
		else if (!stricmp(NOX("takes shockwave damage"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_TAKES_SHOCKWAVE_DAMAGE;
		else if (!stricmp(NOX("hide from radar"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_DONT_SHOW_ON_RADAR;
		else if (!stricmp(NOX("render flak"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_RENDER_FLAK;
		else if (!stricmp(NOX("ciws"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_CIWS;
		else if (!stricmp(NOX("anti-subsystem beam"), weapon_strings[i]))
			weaponp->wi_flags2 |= WIF2_ANTISUBSYSBEAM;
		else if (!stricmp(NOX("no primary linking"), weapon_strings[i]))
			weaponp->wi_flags3 |= WIF3_NOLINK;
		else if (!stricmp(NOX("same emp time for capships"), weapon_strings[i]))
			weaponp->wi_flags3 |= WIF3_USE_EMP_TIME_FOR_CAPSHIP_TURRETS;
		else if (!stricmp(NOX("no primary linked penalty"), weapon_strings[i]))
			weaponp->wi_flags3 |= WIF3_NO_LINKED_PENALTY;
		else if (!stricmp(NOX("no homing speed ramp"), weapon_strings[i]))
			weaponp->wi_flags3 |= WIF3_NO_HOMING_SPEED_RAMP;
		else if (!stricmp(NOX("pulls aspect seekers"), weapon_strings[i]))
		{
			if (!(weaponp->wi_flags & WIF_CMEASURE))
			{
				Warning(LOCATION, "\"pulls aspect seekers\" may only be used for countermeasures!");
			}
			else
			{
				weaponp->wi_flags3 |= WIF3_CMEASURE_ASPECT_HOME_ON;
			}
		}
		else if (!stricmp(NOX("interceptable"), weapon_strings[i]))
			weaponp->wi_flags3 |= WIF3_TURRET_INTERCEPTABLE | WIF3_FIGHTER_INTERCEPTABLE;
		else if (!stricmp(NOX("turret interceptable"), weapon_strings[i]))
			weaponp->wi_flags3 |= WIF3_TURRET_INTERCEPTABLE;
		else if (!stricmp(NOX("fighter interceptable"), weapon_strings[i]))
			weaponp->wi_flags3 |= WIF3_FIGHTER_INTERCEPTABLE;
		else if (!stricmp(NOX("apply recoil"), weapon_strings[i]))
			weaponp->wi_flags3 |= WIF3_APPLY_RECOIL;
		else if (!stricmp(NOX("don't spawn if shot"), weapon_strings[i]))
			weaponp->wi_flags3 |= WIF3_DONT_SPAWN_IF_SHOT;
		else if (!stricmp(NOX("die on lost lock"), weapon_strings[i])) {
			if (!(weaponp->wi_flags & WIF_LOCKED_HOMING)) {
				Warning(LOCATION, "\"die on lost lock\" may only be used for Homing Type ASPECT/JAVELIN!");
			}
			else {
				weaponp->wi_flags3 |= WIF3_DIE_ON_LOST_LOCK;
			}
		}
		else
			Warning(LOCATION, "Bogus string in weapon flags: %s\n", weapon_strings[i]);
	}

	// Goober5000 - fix up pierce/nopierce flags, per Mantis #2442
	if (set_pierce)
		weaponp->wi_flags2 |= WIF2_PIERCE_SHIELDS;
	if (set_nopierce)
		weaponp->wi_flags2 &= ~WIF2_PIERCE_SHIELDS;

	// set default tech room status - Goober5000
	if (weaponp->wi_flags & WIF_IN_TECH_DATABASE)
		weaponp->wi_flags2 |= WIF2_DEFAULT_IN_TECH_DATABASE;

	// SWARM, CORKSCREW and FLAK should be mutually exclusive
	if (weaponp->wi_flags & WIF_FLAK)
	{
		if ((weaponp->wi_flags & WIF_SWARM) || (weaponp->wi_flags & WIF_CORKSCREW))
		{
			Warning(LOCATION, "Swarm, Corkscrew, and Flak are mutually exclusive!  Removing Swarm and Corkscrew attributes.\n");
			weaponp->wi_flags &= ~WIF_SWARM;
			weaponp->wi_flags &= ~WIF_CORKSCREW;
		}
	}
	else
	{
		if ((weaponp->wi_flags & WIF_SWARM) && (weaponp->wi_flags & WIF_CORKSCREW))
		{
			Warning(LOCATION, "Swarm and Corkscrew are mutually exclusive!  Defaulting to Swarm.\n");
			weaponp->wi_flags &= ~WIF_CORKSCREW;
		}
	}

	if (weaponp->wi_flags2 & WIF2_LOCAL_SSM)
	{
		if (!(weaponp->wi_flags & WIF_HOMING) || (weaponp->subtype !=WP_MISSILE))
		{
			Warning(LOCATION, "local ssm must be guided missile: %s", weaponp->name);
		}
	}

	if ((weaponp->wi_flags2 & WIF2_SMALL_ONLY) && (weaponp->wi_flags & WIF_HUGE))
	{
		Warning(LOCATION,"\"small only\" and \"huge\" flags are mutually exclusive.\nThey are used together in %s\nAI will most likely not use this weapon",weaponp->name);
	}

	if (!(weaponp->wi_flags & WIF_SPAWN) && (weaponp->wi_flags2 & WIF2_SMART_SPAWN))
	{
		Warning(LOCATION,"\"smart spawn\" flag used without \"spawn\" flag in %s\n",weaponp->name);
	}

	if ((weaponp->wi_flags2 & WIF2_INHERIT_PARENT_TARGET) && (!(weaponp->wi_flags & WIF_CHILD)))
	{
		Warning(LOCATION,"Weapon %s has the \"inherit parent target\" flag, but not the \"child\" flag.  No changes in behavior will occur.", weaponp->name);
	}

	if (!(weaponp->wi_flags & WIF_HOMING_HEAT) && (weaponp->wi_flags2 & WIF2_UNTARGETED_HEAT_SEEKER))
	{
		Warning(LOCATION,"Weapon '%s' has the \"untargeted heat seeker\" flag, but Homing Type is not set to \"HEAT\".", weaponp->name);
	}
}

void parse_shockwave_info(shockwave_create_info *sci, char *pre_char)
{
	char buf[NAME_LENGTH];

	sprintf(buf, "%sShockwave damage:", pre_char);
	if(optional_string(buf)) {
		stuff_float(&sci->damage);
	}

	sprintf(buf, "%sShockwave damage type:", pre_char);
	if(optional_string(buf)) {
		stuff_string(buf, F_NAME, NAME_LENGTH);
		sci->damage_type_idx_sav = damage_type_add(buf);
		sci->damage_type_idx = sci->damage_type_idx_sav;
	}

	sprintf(buf, "%sBlast Force:", pre_char);
	if(optional_string(buf)) {
		stuff_float(&sci->blast);
	}

	sprintf(buf, "%sInner Radius:", pre_char);
	if(optional_string(buf)) {
		stuff_float(&sci->inner_rad);
	}

	sprintf(buf, "%sOuter Radius:", pre_char);
	if(optional_string(buf)) {
		stuff_float(&sci->outer_rad);
	}

	if (sci->outer_rad < sci->inner_rad) {
		Warning(LOCATION, "Shockwave outer radius must be greater than or equal to the inner radius!");
		sci->outer_rad = sci->inner_rad;
	}

	sprintf(buf, "%sShockwave Speed:", pre_char);
	if(optional_string(buf)) {
		stuff_float(&sci->speed);
	}

	sprintf(buf, "%sShockwave Rotation:", pre_char);
	if(optional_string(buf)) {
		float angs[3];
		stuff_float_list(angs, 3);
		for(int i = 0; i < 3; i++)
		{
			angs[i] = angs[i] * (PI2/180.0f);
			while(angs[i] < 0)
			{
				angs[i] += PI2;
			}
			while(angs[i] > PI2)
			{
				angs[i] -= PI2;
			}
		}
		sci->rot_angles.p = angs[0];
		sci->rot_angles.b = angs[1];
		sci->rot_angles.h = angs[2];
	}

	sprintf(buf, "%sShockwave Model:", pre_char);
	if(optional_string(buf)) {
		stuff_string(sci->pof_name, F_NAME, MAX_FILENAME_LEN);
	}

	sprintf(buf, "%sShockwave Name:", pre_char);
	if(optional_string(buf)) {
		stuff_string(sci->name, F_NAME, MAX_FILENAME_LEN);
	}
}

void init_weapon_entry(int weap_info_index)
{
	Assert(weap_info_index > -1 && weap_info_index < MAX_WEAPON_TYPES);
	weapon_info *wip = &Weapon_info[weap_info_index];
	int i, j;

	wip->wi_flags = WIF_DEFAULT_VALUE;
	wip->wi_flags2 = WIF2_DEFAULT_VALUE;

	wip->subtype = WP_UNUSED;
	wip->render_type = WRT_NONE;

	memset(wip->name, 0, sizeof(wip->name));
	memset(wip->title, 0, sizeof(wip->title));
	wip->desc = NULL;

	memset(wip->tech_title, 0, sizeof(wip->tech_title));
	memset(wip->tech_anim_filename, 0, sizeof(wip->tech_anim_filename));
	wip->tech_desc = NULL;
	memset(wip->tech_model, 0, sizeof(wip->tech_model));
	
	memset(wip->hud_filename, 0, sizeof(wip->hud_filename));
	wip->hud_image_index = -1;

	memset(wip->pofbitmap_name, 0, sizeof(wip->pofbitmap_name));

	wip->model_num = -1;
	wip->hud_target_lod = -1;
	wip->num_detail_levels = -1;
	for ( i = 0; i < MAX_MODEL_DETAIL_LEVELS; i++ )
	{
		wip->detail_distance[i] = -1;
	}

	vm_vec_zero(&wip->closeup_pos);
	wip->closeup_zoom = 1.0f;

	generic_anim_init(&wip->laser_bitmap);
	generic_anim_init(&wip->laser_glow_bitmap);

	gr_init_color(&wip->laser_color_1, 255, 255, 255);
	gr_init_color(&wip->laser_color_2, 255, 255, 255);
	
	wip->laser_length = 10.0f;
	wip->laser_head_radius = 1.0f;
	wip->laser_tail_radius = 1.0f;
	
	memset(wip->external_model_name, 0, sizeof(wip->external_model_name));
	wip->external_model_num = -1;
	
	wip->weapon_submodel_rotate_accell = 10.0f;
	wip->weapon_submodel_rotate_vel = 0.0f;
	
	wip->mass = 1.0f;
	wip->max_speed = 10.0f;
	wip->acceleration_time = 0.0f;
	wip->vel_inherit_amount = 1.0f;
	wip->free_flight_time = 0.0f;
	wip->fire_wait = 1.0f;
	wip->max_delay = 0.0f;
	wip->min_delay = 0.0f;
	wip->damage = 0.0f;
	wip->damage_time = -1.0f;
	wip->atten_damage = -1.0f;

	wip->damage_type_idx = -1;
	wip->damage_type_idx_sav = -1;

	wip->armor_type_idx = -1;

	wip->arm_time = 0;
	wip->arm_dist = 0.0f;
	wip->arm_radius = 0.0f;
	wip->det_range = 0.0f;
	wip->det_radius = 0.0f;
	wip->flak_targeting_accuracy = 60.0f; // Standard value as defined in flak.cpp
	wip->flak_detonation_accuracy = 65.0f;
	wip->untargeted_flak_range_penalty = 20.0f;
	
	wip->armor_factor = 1.0f;
	wip->shield_factor = 1.0f;
	wip->subsystem_factor = 1.0f;
	
	wip->life_min = -1.0f;
	wip->life_max = -1.0f;
	wip->lifetime = 1.0f;
	wip->energy_consumed = 0.0f;

	wip->cargo_size = 1.0f;
	
	wip->turn_time = 1.0f;
	wip->fov = 0;				//should be cos(pi), not pi
	
	wip->min_lock_time = 0.0f;
	wip->lock_pixels_per_sec = 50;
	wip->catchup_pixels_per_sec = 50;
	wip->catchup_pixel_penalty = 50;
	wip->seeker_strength = 1.0f;

	wip->swarm_count = -1;
	// *Default is 150  -Et1
	wip->SwarmWait = SWARM_MISSILE_DELAY;
	
	wip->pre_launch_snd = -1;
	wip->pre_launch_snd_min_interval = 0;

	wip->launch_snd = -1;
	wip->impact_snd = -1;
	wip->disarmed_impact_snd = -1;
	wip->flyby_snd = -1;
	
	wip->rearm_rate = 1.0f;
	
	wip->weapon_range = 999999999.9f;
	// *Minimum weapon range, default is 0 -Et1
	wip->WeaponMinRange = 0.0f;

    wip->num_spawn_weapons_defined = 0;

    for (i = 0; i < MAX_SPAWN_TYPES_PER_WEAPON; i++)
    {
	    wip->spawn_info[i].spawn_type  = -1;
	    wip->spawn_info[i].spawn_angle = 180;
        wip->spawn_info[i].spawn_count = DEFAULT_WEAPON_SPAWN_COUNT;
    }
	
	// Trails
	wip->tr_info.pt = vmd_zero_vector;
	wip->tr_info.w_start = 1.0f;
	wip->tr_info.w_end = 1.0f;
	wip->tr_info.a_start = 1.0f;
	wip->tr_info.a_end = 1.0f;
	wip->tr_info.max_life = 1.0f;
	wip->tr_info.stamp = 0;
	generic_bitmap_init(&wip->tr_info.texture, NULL);
	wip->tr_info.n_fade_out_sections = 0;

	memset(wip->icon_filename, 0, sizeof(wip->icon_filename));

	memset(wip->anim_filename, 0, sizeof(wip->anim_filename));

	wip->impact_explosion_radius = 1.0f;
	wip->impact_weapon_expl_index = -1;

	wip->shield_impact_explosion_radius = 1.0f;

	wip->dinky_impact_explosion_radius = 1.0f;
	wip->dinky_impact_weapon_expl_index = -1;

	wip->flash_impact_weapon_expl_index = -1;
	wip->flash_impact_explosion_radius = 0.0f;

	wip->piercing_impact_explosion_radius = 0.0f;
	wip->piercing_impact_particle_count = 0;
	wip->piercing_impact_particle_life = 0.0f;
	wip->piercing_impact_particle_velocity = 0.0f;
	wip->piercing_impact_weapon_expl_index = -1;
	wip->piercing_impact_particle_back_velocity = 0.0f;
	wip->piercing_impact_particle_variance = 0.0f;

	wip->muzzle_flash = -1;

	wip->emp_intensity = EMP_DEFAULT_INTENSITY;
	wip->emp_time = EMP_DEFAULT_TIME;	// Goober5000: <-- Look!  I fixed a Volition bug!  Gimme $5, Dave!
	wip->recoil_modifier = 1.0f;
	wip->weapon_reduce = ESUCK_DEFAULT_WEAPON_REDUCE;
	wip->afterburner_reduce = ESUCK_DEFAULT_AFTERBURNER_REDUCE;

	//customizeable corkscrew stuff
	wip->cs_num_fired=4;
	wip->cs_radius=1.25f;
	wip->cs_delay=30;
	wip->cs_crotate=1;
	wip->cs_twist=5.0f;
	
	wip->elec_time=8000;
	wip->elec_eng_mult=1.0f;
	wip->elec_weap_mult=1.0f;
	wip->elec_beam_mult=1.0f;
	wip->elec_sensors_mult=1.0f;
	wip->elec_randomness=2000;
	wip->elec_use_new_style=0;

	wip->lssm_warpout_delay=0;			//delay between launch and warpout (ms)
	wip->lssm_warpin_delay=0;			//delay between warpout and warpin (ms)
	wip->lssm_stage5_vel=0;		//velocity during final stage
	wip->lssm_warpin_radius=0;
	wip->lssm_lock_range=1000000.0f;	//local ssm lock range (optional)

	wip->cm_aspect_effectiveness = 1.0f;
	wip->cm_heat_effectiveness = 1.0f;
	wip->cm_effective_rad = MAX_CMEASURE_TRACK_DIST;
	wip->cm_detonation_rad = CMEASURE_DETONATE_DISTANCE;
	wip->cm_kill_single = false;

	wip->b_info.beam_type = -1;
	wip->b_info.beam_life = -1.0f;
	wip->b_info.beam_warmup = -1;
	wip->b_info.beam_warmdown = -1;
	wip->b_info.beam_muzzle_radius = 0.0f;
	wip->b_info.beam_particle_count = -1;
	wip->b_info.beam_particle_radius = 0.0f;
	wip->b_info.beam_particle_angle = 0.0f;
	wip->b_info.beam_loop_sound = -1;
	wip->b_info.beam_warmup_sound = -1;
	wip->b_info.beam_warmdown_sound = -1;
	wip->b_info.beam_num_sections = 0;
	wip->b_info.glow_length = 0;
	wip->b_info.directional_glow = false;
	wip->b_info.beam_shots = 1;
	wip->b_info.beam_shrink_factor = 0.0f;
	wip->b_info.beam_shrink_pct = 0.0f;
	wip->b_info.range = BEAM_FAR_LENGTH;
	wip->b_info.damage_threshold = 1.0f;
	wip->b_info.beam_width = -1.0f;

	generic_anim_init(&wip->b_info.beam_glow, NULL);
	generic_anim_init(&wip->b_info.beam_particle_ani, NULL);

	for (i = 0; i < MAX_IFFS; i++)
		for (j = 0; j < NUM_SKILL_LEVELS; j++)
			wip->b_info.beam_iff_miss_factor[i][j] = 0.00001f;
	
	//WMC - Okay, so this is needed now
	beam_weapon_section_info *bsip;
	for (i = 0; i < MAX_BEAM_SECTIONS; i++) {
		bsip = &wip->b_info.sections[i];

		generic_anim_init(&bsip->texture, NULL);

		bsip->width = 1.0f;
		bsip->flicker = 0.1f;
		bsip->z_add = i2fl(MAX_BEAM_SECTIONS - i - 1);
		bsip->tile_type = 0;
		bsip->tile_factor = 1.0f;
		bsip->translation = 0.0f;
	}

	for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) {						// default values for everything -nuke
		wip->particle_spewers[s].particle_spew_type = PSPEW_NONE;				// added by nuke
		wip->particle_spewers[s].particle_spew_count = 1;
		wip->particle_spewers[s].particle_spew_time = 25;
		wip->particle_spewers[s].particle_spew_vel = 0.4f;
		wip->particle_spewers[s].particle_spew_radius = 2.0f;
		wip->particle_spewers[s].particle_spew_lifetime = 0.15f;
		wip->particle_spewers[s].particle_spew_scale = 0.8f;
		wip->particle_spewers[s].particle_spew_z_scale = 1.0f;			// added by nuke
		wip->particle_spewers[s].particle_spew_rotation_rate = 10.0f;
		wip->particle_spewers[s].particle_spew_offset = vmd_zero_vector;
		wip->particle_spewers[s].particle_spew_velocity = vmd_zero_vector;
		generic_anim_init(&wip->particle_spewers[s].particle_spew_anim, NULL);
	}
	
	wip->tag_level = -1;
	wip->tag_time = -1.0f;
	
	wip->SSM_index =-1;				// tag C SSM index, wich entry in the SSM table this weapon calls -Bobboau
	
	wip->field_of_fire = 0.0f;
	wip->fof_spread_rate = 0.0f;
	wip->fof_reset_rate = 0.0f;
	wip->max_fof_spread = 0.0f;
	
	wip->shots = 1;

	wip->alpha_max = 1.0f;
	wip->alpha_min = 0.0f;
	wip->alpha_cycle = 0.0f;

	shockwave_create_info_init(&wip->shockwave);
	shockwave_create_info_init(&wip->dinky_shockwave);

	wip->weapon_hitpoints = 0;

	wip->burst_delay = 1.0f; // 1 second, just incase its not defined
	wip->burst_shots = 0;
	wip->burst_flags = 0;

	generic_anim_init( &wip->thruster_flame );
	generic_anim_init( &wip->thruster_glow );
	
	wip->thruster_glow_factor = 1.0f;
	wip->target_lead_scaler = 0.0f;

	wip->selection_effect = Default_weapon_select_effect;

	wip->hud_locked_snd = -1;
	wip->hud_tracking_snd = -1;
	wip->hud_in_flight_snd = -1;
}

// function to parse the information for a specific weapon type.	
// return 0 if successful, otherwise return -1
#define WEAPONS_MULTITEXT_LENGTH 2048

int parse_weapon(int subtype, bool replace, const char *filename)
{
	char buf[WEAPONS_MULTITEXT_LENGTH];
	weapon_info *wip = NULL;
	char fname[NAME_LENGTH];
	int iff, idx;
	int primary_rearm_rate_specified=0;
	bool first_time = false;
	bool create_if_not_found  = true;
	int wi_flags = WIF_DEFAULT_VALUE;
	int wi_flags2 = WIF2_DEFAULT_VALUE;
	int wi_flags3 = WIF3_DEFAULT_VALUE;

	required_string("$Name:");
	stuff_string(fname, F_NAME, NAME_LENGTH);
	diag_printf ("Weapon name -- %s\n", fname);

	if(optional_string("+nocreate")) {
		if(!replace) {
			Warning(LOCATION, "+nocreate flag used for weapon in non-modular table");
		}
		create_if_not_found = false;
	}

	//Remove @ symbol
	//these used to be used to denote weapons that would
	//only be parsed in demo builds
	if ( fname[0] == '@' ) {
		backspace(fname);
	}

	int w_id = weapon_info_lookup(fname);

	if(w_id != -1)
	{
		wip = &Weapon_info[w_id];
		if(!replace)
		{
			Warning(LOCATION, "Weapon name %s already exists in weapons.tbl.  All weapon names must be unique; the second entry has been skipped", wip->name);
			if ( !skip_to_start_of_string_either("$Name:", "#End")) {
				Int3();
			}
			return -1;
		}
	}
	else
	{
		//Don't create weapon if it has +nocreate and is in a modular table.
		if(!create_if_not_found && replace)
		{
			if ( !skip_to_start_of_string_either("$Name:", "#End")) {
				Int3();
			}

			return -1;
		}

		if(Num_weapon_types >= MAX_WEAPON_TYPES) {
			Error(LOCATION, "Too many weapon classes before '%s'; maximum is %d.\n", fname, MAX_WEAPON_TYPES);
		}

		wip = &Weapon_info[Num_weapon_types];
		init_weapon_entry(Num_weapon_types);
		first_time = true;
		
		strcpy_s(wip->name, fname);
		Num_weapon_types++;
	}

	if(optional_string("$Alt name:"))
		stuff_string(wip->alt_name, F_NAME, NAME_LENGTH);

	//Set subtype
	if(optional_string("$Subtype:"))
	{
		stuff_string(fname, F_NAME, NAME_LENGTH);

		if(!stricmp("Primary", fname)) {
			wip->subtype = WP_LASER;
		} else if(!stricmp("Secondary", fname)) {
			wip->subtype = WP_MISSILE;
		} else {
			Warning(LOCATION, "Unknown subtype on weapon '%s'", wip->name);
		}
	}
	else if(wip->subtype != WP_UNUSED && !first_time)
	{
		if(wip->subtype != subtype) {
			Warning(LOCATION, "Type of weapon %s entry does not agree with original entry type.", wip->name);
		}
	}
	else
	{
		wip->subtype = subtype;
	}

	if (optional_string("+Title:")) {
		stuff_string(wip->title, F_NAME, WEAPON_TITLE_LEN);
	}

	if (optional_string("+Description:")) {
		if (wip->desc != NULL) {
			vm_free(wip->desc);
			wip->desc = NULL;
		}

		stuff_malloc_string(&wip->desc, F_MULTITEXT);
	}

	if (optional_string("+Tech Title:")) {
		stuff_string(wip->tech_title, F_NAME, NAME_LENGTH);
	}

	if (optional_string("+Tech Anim:")) {
		stuff_string(wip->tech_anim_filename, F_NAME, MAX_FILENAME_LEN);
	}

	if (optional_string("+Tech Description:")) {
		if (wip->tech_desc != NULL) {
			vm_free(wip->tech_desc);
			wip->tech_desc = NULL;
		}

		stuff_malloc_string(&wip->tech_desc, F_MULTITEXT);
	}

	if (optional_string("$Tech Model:")) {
		stuff_string(wip->tech_model, F_NAME, MAX_FILENAME_LEN);

		if (optional_string("+Closeup_pos:")) {
			stuff_vec3d(&wip->closeup_pos);
		}

		if (optional_string("+Closeup_zoom:")) {
			stuff_float(&wip->closeup_zoom);
		}
	}

	// Weapon fadein effect, used when no ani is specified or weapon_select_3d is active
	wip->selection_effect = Default_weapon_select_effect; // By default, use the FS2 effect
	if(optional_string("$Selection Effect:")) {
		char effect[NAME_LENGTH];
		stuff_string(effect, F_NAME, NAME_LENGTH);
		if (!stricmp(effect, "FS2"))
			wip->selection_effect = 2;
		else if (!stricmp(effect, "FS1"))
			wip->selection_effect = 1;
		else if (!stricmp(effect, "off"))
			wip->selection_effect = 0;
	}	

	//Check for the HUD image string
	if(optional_string("$HUD Image:")) {
		stuff_string(wip->hud_filename, F_NAME, MAX_FILENAME_LEN);
	}

	//	Read the model file.  It can be a POF file or none.
	//	If there is no model file (Model file: = "none") then we use our special
	//	laser renderer which requires inner, middle and outer information.
	if ( optional_string("$Model file:") ) {
		stuff_string(wip->pofbitmap_name, F_NAME, MAX_FILENAME_LEN);

		if ( VALID_FNAME(wip->pofbitmap_name) )
			wip->render_type = WRT_POF;

		diag_printf("Model pof file -- %s\n", wip->pofbitmap_name );
	}

	// a special LOD level to use when rendering the weapon in the hud targetbox
	if ( optional_string( "$POF target LOD:" ) )
		stuff_int(&wip->hud_target_lod);

	if(optional_string("$Detail distance:")) {
		wip->num_detail_levels = stuff_int_list(wip->detail_distance, MAX_MODEL_DETAIL_LEVELS, RAW_INTEGER_TYPE);
	}

	if ( optional_string("$External Model File:") )
		stuff_string(wip->external_model_name, F_NAME, MAX_FILENAME_LEN);	

	if ( optional_string("$Submodel Rotation Speed:") )
		stuff_float(&wip->weapon_submodel_rotate_vel);

	if ( optional_string("$Submodel Rotation Acceleration:") )
		stuff_float(&wip->weapon_submodel_rotate_accell);

	//	No POF or AVI file specified, render as special laser type.(?)
	ubyte r,g,b;

	// laser bitmap itself
	if ( optional_string("@Laser Bitmap:") ) {
		stuff_string(fname, F_NAME, NAME_LENGTH);

		if (wip->render_type == WRT_POF) {
			mprintf(("WARNING:  Weapon '%s' has both LASER and POF render types!  Will only use POF type!\n", wip->name));
			generic_anim_init(&wip->laser_bitmap, NULL);
		} else {
			generic_anim_init(&wip->laser_bitmap, fname);
			wip->render_type = WRT_LASER;
		}
	}

	// optional laser glow
	if ( optional_string("@Laser Glow:") ) {
		stuff_string(fname, F_NAME, NAME_LENGTH);

		if (wip->render_type != WRT_LASER) {
			mprintf(("WARNING:  Laser glow specified on non-LASER type weapon (%s)!\n", wip->name));
			Int3();
		} else {
			generic_anim_init(&wip->laser_glow_bitmap, fname);
		}
	}

	if(optional_string("@Laser Color:"))
	{
		// This might be confusing at first glance. If we're a modular table (!first_time),
		// AND we're providing a new color for the laser (being in this block at all),
		// AND the RGB values for laser_color_1 and laser_color_2 match...
		// THEN we conclude that laser_color_2 wasn't explicitly defined before, and the modder would probably prefer
		// it if the laser didn't suddenly start changing colors from the new to the old over its lifespan. -MageKing17
		bool reset = (!first_time && (
			(wip->laser_color_1.red == wip->laser_color_2.red) &&
			(wip->laser_color_1.green == wip->laser_color_2.green) &&
			(wip->laser_color_1.blue == wip->laser_color_2.blue)));
		stuff_ubyte(&r);
		stuff_ubyte(&g);
		stuff_ubyte(&b);
		gr_init_color( &wip->laser_color_1, r, g, b );
		if (reset) {
			gr_init_color( &wip->laser_color_2, wip->laser_color_1.red, wip->laser_color_1.green, wip->laser_color_1.blue );
		}
	}

	// optional string for cycling laser colors
	if(optional_string("@Laser Color2:")){
		stuff_ubyte(&r);
		stuff_ubyte(&g);
		stuff_ubyte(&b);
		gr_init_color( &wip->laser_color_2, r, g, b );
	} else if (first_time) {
		gr_init_color( &wip->laser_color_2, wip->laser_color_1.red, wip->laser_color_1.green, wip->laser_color_1.blue );
	}

	if(optional_string("@Laser Length:")) {
		stuff_float(&wip->laser_length);
	}
	
	if(optional_string("@Laser Head Radius:")) {
		stuff_float(&wip->laser_head_radius);
	}

	if(optional_string("@Laser Tail Radius:")) {
		stuff_float(&wip->laser_tail_radius );
	}

	if(optional_string("$Mass:")) {
		stuff_float( &(wip->mass) );

		// Goober5000 - hack in order to make the beam whack behavior of these three beams match all other beams
		// this relies on Bobboau's beam whack hack in beam_apply_whack()
		if ((!strcmp(wip->name, "SAAA") && (wip->mass == 4.0f))
			|| (!strcmp(wip->name, "MjolnirBeam") && (wip->mass == 1000.0f))
			|| (!strcmp(wip->name, "MjolnirBeam#home") && (wip->mass == 1000.0f)))
		{
			wip->mass = 100.0f;
		}

		diag_printf ("Weapon mass -- %7.3f\n", wip->mass);
	}

	if(optional_string("$Velocity:")) {
		stuff_float( &(wip->max_speed) );
		diag_printf ("Weapon mass -- %7.3f\n", wip->max_speed);
	}

	if(optional_string("$Fire Wait:")) {
		stuff_float( &(wip->fire_wait) );
		diag_printf ("Weapon fire wait -- %7.3f\n", wip->fire_wait);
		// Min and max delay stuff for weapon fire wait randomization
		if (optional_string("+Max Delay:")) {
			stuff_float(&(wip->max_delay));
			diag_printf("Weapon fire max delay -- %7.3f\n", wip->max_delay);
		}
		if (optional_string("+Min Delay:")) {
			stuff_float(&(wip->min_delay));
			diag_printf("Weapon fire min delay -- %7.3f\n", wip->min_delay);
		}
	}

	if(optional_string("$Damage:")) {
		stuff_float(&wip->damage);
		//WMC - now that shockwave damage can be set for them individually,
		//do this automagically
		if(first_time) {
			wip->shockwave.damage = wip->damage;
		}
	}

	// Attenuation of non-beam primary weapon damage
	if(optional_string("$Damage Time:")) {
		stuff_float(&wip->damage_time);
		if(optional_string("+Attenuation Damage:")){
			stuff_float(&wip->atten_damage);
		} else if (optional_string_either("+Min Damage:", "+Max Damage:")) {
			Warning(LOCATION, "+Min Damage: and +Max Damage: in %s are deprecated, please change to +Attenuation Damage:.", wip->name);
			stuff_float(&wip->atten_damage);
		}
	}
	
	if(optional_string("$Damage Type:")) {
		//This is checked for validity on every armor type
		//If it's invalid (or -1), then armor has no effect
		stuff_string(buf, F_NAME, WEAPONS_MULTITEXT_LENGTH);
		wip->damage_type_idx_sav = damage_type_add(buf);
		wip->damage_type_idx = wip->damage_type_idx_sav;
	}

	if(optional_string("$Arm time:")) {
		float flit;
		stuff_float(&flit);
		wip->arm_time = fl2f(flit);
	}

	if(optional_string("$Arm distance:")) {
		stuff_float(&wip->arm_dist);
	}

	if(optional_string("$Arm radius:")) {
		stuff_float(&wip->arm_radius);
	}

	if(optional_string("$Detonation Range:")) {
		stuff_float(&wip->det_range);
	}

	if(optional_string("$Detonation Radius:")) {
		stuff_float(&wip->det_radius);
	}

	if(optional_string("$Flak Detonation Accuracy:")) {
		stuff_float(&wip->flak_detonation_accuracy);
	}

	if(optional_string("$Flak Targeting Accuracy:")) {
		stuff_float(&wip->flak_targeting_accuracy);
	}

	if(optional_string("$Untargeted Flak Range Penalty:")) {
		stuff_float(&wip->untargeted_flak_range_penalty);
	}

	parse_shockwave_info(&wip->shockwave, "$");

	//Retain compatibility
	if(first_time)
	{
		wip->dinky_shockwave = wip->shockwave;
		wip->dinky_shockwave.damage /= 4.0f;
	}

	if(optional_string("$Dinky shockwave:"))
	{
		parse_shockwave_info(&wip->dinky_shockwave, "+");
	}

	if(optional_string("$Armor Factor:")) {
		stuff_float(&wip->armor_factor);
	}

	if(optional_string("$Shield Factor:")) {
		stuff_float(&wip->shield_factor);
	}

	if(optional_string("$Subsystem Factor:")) {
		stuff_float(&wip->subsystem_factor);
	}

	if(optional_string("$Lifetime Min:")) {
		stuff_float(&wip->life_min);

		if(wip->life_min < 0.0f) {
			wip->life_min = 0.0f;
			Warning(LOCATION, "Lifetime min for weapon '%s' cannot be less than 0. Setting to 0.\n", wip->name);
		}
	}

	if(optional_string("$Lifetime Max:")) {
		stuff_float(&wip->life_max);

		if(wip->life_max < 0.0f) {
			wip->life_max = 0.0f;
			Warning(LOCATION, "Lifetime max for weapon '%s' cannot be less than 0. Setting to 0.\n", wip->name);
		} else if (wip->life_max < wip->life_min) {
			wip->life_max = wip->life_min + 0.1f;
			Warning(LOCATION, "Lifetime max for weapon '%s' cannot be less than its Lifetime Min (%f) value. Setting to %f.\n", wip->name, wip->life_min, wip->life_max);
		} else {
			wip->lifetime = (wip->life_min+wip->life_max)*0.5f;
		}
	}

	if(wip->life_min >= 0.0f && wip->life_max < 0.0f) {
		wip->lifetime = wip->life_min;
		wip->life_min = -1.0f;
		Warning(LOCATION, "Lifetime min, but not lifetime max, specified for weapon %s. Assuming static lifetime of %.2f seconds.\n", wip->name, wip->lifetime);
	}

	if(optional_string("$Lifetime:")) {
		if(wip->life_min >= 0.0f || wip->life_max >= 0.0f) {
			Warning(LOCATION, "Lifetime min or max specified, but $Lifetime was also specified; min or max will be used.");
		}
		stuff_float(&wip->lifetime);
		if (wip->damage_time > wip->lifetime) {
			Warning(LOCATION, "Lifetime is lower than Damage Time, setting Damage Time to be one half the value of Lifetime.");
			wip->damage_time = 0.5f * wip->lifetime;
		}
	}

	if(optional_string("$Energy Consumed:")) {
		stuff_float(&wip->energy_consumed);
	}

	// Goober5000: cargo size is checked for div-0 errors... see below (must parse flags first)
	if(optional_string("$Cargo Size:"))
	{
		stuff_float(&wip->cargo_size);
	}

	bool is_homing=false;
	if(optional_string("$Homing:")) {
		stuff_boolean(&is_homing);
	}

	if (is_homing || (wip->wi_flags & WIF_HOMING))
	{
		char	temp_type[NAME_LENGTH];

		// the following five items only need to be recorded if the weapon is a homing weapon
		if(optional_string("+Type:"))
		{
			stuff_string(temp_type, F_NAME, NAME_LENGTH);
			if (!stricmp(temp_type, NOX("HEAT")))
			{
				if(wip->wi_flags & WIF_HOMING_ASPECT) {
					wip->wi_flags &= ~WIF_HOMING_ASPECT;
				}
				if(wip->wi_flags & WIF_HOMING_JAVELIN) {
					wip->wi_flags &= ~WIF_HOMING_JAVELIN;
				}

				wip->wi_flags |= WIF_HOMING_HEAT | WIF_TURNS;
				wi_flags |= WIF_HOMING_HEAT | WIF_TURNS;
			}
			else if (!stricmp(temp_type, NOX("ASPECT")))
			{
				if(wip->wi_flags & WIF_HOMING_HEAT) {
					wip->wi_flags &= ~WIF_HOMING_HEAT;
				}
				if(wip->wi_flags & WIF_HOMING_JAVELIN) {
					wip->wi_flags &= ~WIF_HOMING_JAVELIN;
				}

				wip->wi_flags |= WIF_HOMING_ASPECT | WIF_TURNS;
				wi_flags |= WIF_HOMING_ASPECT | WIF_TURNS;
			}
			else if (!stricmp(temp_type, NOX("JAVELIN")))
			{
				if(wip->wi_flags & WIF_HOMING_HEAT) {
					wip->wi_flags &= ~WIF_HOMING_HEAT;
				}
				if(wip->wi_flags & WIF_HOMING_ASPECT) {
					wip->wi_flags &= ~WIF_HOMING_ASPECT;
				}

				wip->wi_flags |= (WIF_HOMING_JAVELIN | WIF_TURNS);
				wi_flags |= (WIF_HOMING_JAVELIN | WIF_TURNS);
			}
			//If you want to add another weapon, remember you need to reset
			//ALL homing flags.
		}

		if (wip->wi_flags & WIF_HOMING_HEAT)
		{
			float	view_cone_angle;

			if(optional_string("+Turn Time:")) {
				stuff_float(&wip->turn_time);
			}

			if(optional_string("+View Cone:")) {
				stuff_float(&view_cone_angle);
				wip->fov = (float)cos((float)(ANG_TO_RAD(view_cone_angle/2.0f)));
			}

			if (optional_string("+Seeker Strength:"))
			{
				//heat default seeker strength is 3
				stuff_float(&wip->seeker_strength);
				wip->wi_flags2 |= WIF2_CUSTOM_SEEKER_STR;
				if (wip->seeker_strength <= 0)
				{
					Warning(LOCATION,"Seeker Strength for missile \'%s\' must be greater than zero\nReseting value to default.", wip->name);
					wip->seeker_strength = 3.0f;
					wip->wi_flags2 &= ~WIF2_CUSTOM_SEEKER_STR;
				}
			}
			else
			{
				if(!(wip->wi_flags2 & WIF2_CUSTOM_SEEKER_STR))
					wip->seeker_strength = 3.0f;
			}

			if (optional_string("+Target Lead Scaler:"))
			{
				stuff_float(&wip->target_lead_scaler);
				if (wip->target_lead_scaler == 0.0f)
					wip->wi_flags2 &= ~WIF2_VARIABLE_LEAD_HOMING;
				else {
					wip->wi_flags2 |= WIF2_VARIABLE_LEAD_HOMING;
					wi_flags2 |= WIF2_VARIABLE_LEAD_HOMING;
				}
			}
		}
		else if ((wip->wi_flags & WIF_HOMING_ASPECT) || (wip->wi_flags & WIF_HOMING_JAVELIN))
		{
			if(optional_string("+Turn Time:")) {
				stuff_float(&wip->turn_time);
			}

			if(optional_string("+View Cone:")) {
				float	view_cone_angle;
				stuff_float(&view_cone_angle);
				wip->fov = (float)cos((float)(ANG_TO_RAD(view_cone_angle/2.0f)));
			}

			if(optional_string("+Min Lock Time:")) {			// minimum time (in seconds) to achieve lock
				stuff_float(&wip->min_lock_time);
			}

			if(optional_string("+Lock Pixels/Sec:")) {		// pixels/sec moved while locking
				stuff_int(&wip->lock_pixels_per_sec);
			}

			if(optional_string("+Catch-up Pixels/Sec:")) {	// pixels/sec moved while catching-up for a lock
				stuff_int(&wip->catchup_pixels_per_sec);
			}

			if(optional_string("+Catch-up Penalty:")) {
				// number of extra pixels to move while locking as a penalty for catching up for a lock
				stuff_int(&wip->catchup_pixel_penalty);
			}

			if (optional_string("+Seeker Strength:"))
			{
				//aspect default seeker strength is 2
				stuff_float(&wip->seeker_strength);
				wip->wi_flags2 |= WIF2_CUSTOM_SEEKER_STR;
				if (wip->seeker_strength <= 0)
				{
					Warning(LOCATION,"Seeker Strength for missile \'%s\' must be greater than zero\nReseting value to default.", wip->name);
					wip->seeker_strength = 2.0f;
					wip->wi_flags2 &= ~WIF2_CUSTOM_SEEKER_STR;
				}
			} 
			else
			{
				if(!(wip->wi_flags2 & WIF2_CUSTOM_SEEKER_STR))
					wip->seeker_strength = 2.0f;
			}
			if (optional_string("+Target Lead Scaler:"))
			{
				stuff_float(&wip->target_lead_scaler);
				if (wip->target_lead_scaler == 1.0f)
					wip->wi_flags2 &= ~WIF2_VARIABLE_LEAD_HOMING;
				else {
					wip->wi_flags2 |= WIF2_VARIABLE_LEAD_HOMING;
					wi_flags2 |= WIF2_VARIABLE_LEAD_HOMING;
				}
			}

			if (wip->wi_flags & WIF_LOCKED_HOMING) {
				// locked homing missiles have a much longer lifespan than the AI think they do
				wip->max_lifetime = wip->lifetime * LOCKED_HOMING_EXTENDED_LIFE_FACTOR; 
			}
		}
		else
		{
			Error(LOCATION, "Illegal homing type = %s.\nMust be HEAT, ASPECT or JAVELIN.\n", temp_type);
		}

	}

	// swarm missiles
	int s_count;

	if(optional_string("$Swarm:"))
	{
		wip->swarm_count = SWARM_DEFAULT_NUM_MISSILES_FIRED;
		stuff_int(&s_count);
		wip->swarm_count = (short)s_count;

		// flag as being a swarm weapon
		wip->wi_flags |= WIF_SWARM;
		wi_flags |= WIF_SWARM;
	}

	// *Swarm wait token    -Et1
	if((wip->wi_flags & WIF_SWARM) && optional_string( "+SwarmWait:" ))
	{
		float SwarmWait;
		stuff_float( &SwarmWait );
		if( SwarmWait > 0.0f && SwarmWait * wip->swarm_count < wip->fire_wait )
		{
			wip->SwarmWait = int( SwarmWait * 1000 );
		}
	}

	if(optional_string("$Acceleration Time:")) {
		stuff_float(&wip->acceleration_time);
	}

	if(optional_string("$Velocity Inherit:")) {
		stuff_float(&wip->vel_inherit_amount);
		wip->vel_inherit_amount /= 100.0f; // % -> 0..1
	}

	if(optional_string("$Free Flight Time:")) {
		stuff_float(&(wip->free_flight_time));
	} else if(first_time && is_homing) {
		wip->free_flight_time = HOMING_DEFAULT_FREE_FLIGHT_TIME;
	}

	if(optional_string("$Free Flight Speed:")) {
		float temp;
		stuff_float(&temp);
		nprintf(("Warning", "Ignoring free flight speed for weapon '%s'\n", wip->name));
	}
	//Optional one-shot sound to play at the beginning of firing
	parse_sound("$PreLaunchSnd:", &wip->pre_launch_snd, wip->name);

	//Optional delay for Pre-Launch sound
	if(optional_string("+PreLaunchSnd Min Interval:"))
	{
		stuff_int(&wip->pre_launch_snd_min_interval);
	}

	//Launch sound
	parse_sound("$LaunchSnd:", &wip->launch_snd, wip->name);

	//Impact sound
	parse_sound("$ImpactSnd:", &wip->impact_snd, wip->name);

	//Disarmed impact sound
	parse_sound("$Disarmed ImpactSnd:", &wip->impact_snd, wip->name);

	parse_sound("$FlyBySnd:", &wip->flyby_snd, wip->name);

	parse_sound("$TrackingSnd:", &wip->hud_tracking_snd, wip->name);
	
	parse_sound("$LockedSnd:", &wip->hud_locked_snd, wip->name);

	parse_sound("$InFlightSnd:", &wip->hud_in_flight_snd, wip->name);

	if (optional_string("+Inflight sound type:"))
	{
		SCP_string type;

		stuff_string(type, F_NAME);

		if (!stricmp(type.c_str(), "TARGETED"))
		{
			wip->in_flight_play_type = TARGETED;
		}
		else if (!stricmp(type.c_str(), "UNTARGETED"))
		{
			wip->in_flight_play_type = UNTARGETED;
		}
		else if (!stricmp(type.c_str(), "ALWAYS"))
		{
			wip->in_flight_play_type = ALWAYS;
		}
		else
		{
			Warning(LOCATION, "Unknown in-flight sound type \"%s\"!", type.c_str());
			wip->in_flight_play_type = ALWAYS;
		}
	}

	if(optional_string("$Model:"))
	{
		wip->render_type = WRT_POF;
		stuff_string(wip->pofbitmap_name, F_NAME, MAX_FILENAME_LEN);
	}

	// handle rearm rate - modified by Goober5000
	primary_rearm_rate_specified = 0;
	float rearm_rate;
	// Anticipate rearm rate for ballistic primaries
	if (optional_string("$Rearm Rate:"))
	{
		if (subtype != WP_MISSILE) {
			primary_rearm_rate_specified = 1;
		}

		stuff_float( &rearm_rate );
		if (rearm_rate > 0.0f)
		{
			wip->rearm_rate = 1.0f/rearm_rate;
		}
		else
		{
			Warning(LOCATION, "Rearm wait of less than 0 on weapon %s; setting to 1", wip->name);
		}
	}


	if (optional_string("+Weapon Range:")) {
		stuff_float(&wip->weapon_range);
	}

	if( optional_string( "+Weapon Min Range:" ) )
	{
		float MinRange;
		stuff_float( &MinRange );

		if( MinRange > 0.0f && MinRange < MIN( wip->max_speed * wip->lifetime, wip->weapon_range ) )
		{
			wip->WeaponMinRange = MinRange;
		}
		else
		{
			Warning(LOCATION, "Invalid minimum range on weapon %s; setting to 0", wip->name);
		}

	}

	parse_wi_flags(wip, wi_flags, wi_flags2, wi_flags3);

	// be friendly; make sure ballistic flags are synchronized - Goober5000
	// primary
	if (subtype == WP_LASER)
	{
		// ballistic
		if (wip->wi_flags2 & WIF2_BALLISTIC)
		{
			// rearm rate not specified
			if (!primary_rearm_rate_specified && first_time)
			{
				Warning(LOCATION, "$Rearm Rate for ballistic primary %s not specified.  Defaulting to 100...\n", wip->name);
				wip->rearm_rate = 100.0f;
			}
		}
		// not ballistic
		else
		{
			// rearm rate specified
			if (primary_rearm_rate_specified)
			{
				Warning(LOCATION, "$Rearm Rate specified for non-ballistic primary %s\n", wip->name);
			}
		}

	}
	// secondary
	else
	{
		// ballistic
		if (wip->wi_flags2 & WIF2_BALLISTIC)
		{
			Warning(LOCATION, "Secondary weapon %s can't be ballistic.  Removing this flag...\n", wip->name);
			wip->wi_flags2 &= ~WIF2_BALLISTIC;
		}
	}

	// also make sure EMP is friendly - Goober5000
	if (wip->wi_flags & WIF_EMP)
	{
		if (!wip->shockwave.outer_rad)
		{
			Warning(LOCATION, "Outer blast radius of weapon %s is zero - EMP will not work.\nAdd $Outer Radius to weapon table entry.\n", wip->name);
		}
	}

	// also make sure secondaries and ballistic primaries do not have 0 cargo size
	if (subtype == WP_MISSILE || wip->wi_flags2 & WIF2_BALLISTIC)
	{
		if (wip->cargo_size == 0.0f)
		{
			Warning(LOCATION, "Cargo size of weapon %s cannot be 0.  Setting to 1.\n", wip->name);
			wip->cargo_size = 1.0f;
		}
	}

	if ( optional_string("$Trail:") ) {
		trail_info *ti = &wip->tr_info;
		wip->wi_flags |= WIF_TRAIL;		// missile leaves a trail

		if ( optional_string("+Start Width:") )
			stuff_float(&ti->w_start);

		if ( optional_string("+End Width:") )
			stuff_float(&ti->w_end);

		if ( optional_string("+Start Alpha:") )
			stuff_float(&ti->a_start);

		if ( optional_string("+End Alpha:") )
			stuff_float(&ti->a_end);

		if ( optional_string("+Max Life:") ) {
			stuff_float(&ti->max_life);
			ti->stamp = fl2i(1000.0f*ti->max_life)/(NUM_TRAIL_SECTIONS+1);
		}

		if ( required_string("+Bitmap:") ) {
			stuff_string(fname, F_NAME, NAME_LENGTH);
			generic_bitmap_init(&ti->texture, fname);
		}

		if ( optional_string("+Faded Out Sections:") ) {
			stuff_int(&ti->n_fade_out_sections);
		}
	}

	// read in filename for icon that is used in weapons selection
	if ( optional_string("$Icon:") ) {
		stuff_string(wip->icon_filename, F_NAME, MAX_FILENAME_LEN);
	}

	// read in filename for animation that is used in weapons selection
	if ( optional_string("$Anim:") ) {
		stuff_string(wip->anim_filename, F_NAME, MAX_FILENAME_LEN);
	}

	if ( optional_string("$Impact Explosion:") ) {
		stuff_string(fname, F_NAME, NAME_LENGTH);

		if ( VALID_FNAME(fname) )
			wip->impact_weapon_expl_index = Weapon_explosions.Load(fname);
	}
	
	if ( optional_string("$Impact Explosion Radius:") )
		stuff_float(&wip->impact_explosion_radius);

	if ( optional_string("$Shield Impact Explosion Radius:") ) {
		stuff_float(&wip->shield_impact_explosion_radius);
	} else if (first_time) {
		wip->shield_impact_explosion_radius = wip->impact_explosion_radius;
	}

	if ( optional_string("$Dinky Impact Explosion:") ) {
		stuff_string(fname, F_NAME, NAME_LENGTH);

		if ( VALID_FNAME(fname) )
			wip->dinky_impact_weapon_expl_index = Weapon_explosions.Load(fname);
	} else if (first_time) {
		wip->dinky_impact_weapon_expl_index = wip->impact_weapon_expl_index;
	}

	if ( optional_string("$Dinky Impact Explosion Radius:") )
		stuff_float(&wip->dinky_impact_explosion_radius);
	else if (first_time)
		wip->dinky_impact_explosion_radius = wip->impact_explosion_radius;

	if ( optional_string("$Piercing Impact Explosion:") ) {
		stuff_string(fname, F_NAME, NAME_LENGTH);

		if ( VALID_FNAME(fname) )
			wip->piercing_impact_weapon_expl_index = Weapon_explosions.Load(fname);
	}

	if ( optional_string("$Piercing Impact Radius:") )
		stuff_float(&wip->piercing_impact_explosion_radius);

	if ( optional_string("$Piercing Impact Velocity:") )
		stuff_float(&wip->piercing_impact_particle_velocity);

	if ( optional_string("$Piercing Impact Splash Velocity:") )
		stuff_float(&wip->piercing_impact_particle_back_velocity);

	if ( optional_string("$Piercing Impact Variance:") )
		stuff_float(&wip->piercing_impact_particle_variance);

	if ( optional_string("$Piercing Impact Life:") )
		stuff_float(&wip->piercing_impact_particle_life);

	if ( optional_string("$Piercing Impact Particles:") )
		stuff_int(&wip->piercing_impact_particle_count);

	// muzzle flash
	if ( optional_string("$Muzzleflash:") ) {
		stuff_string(fname, F_NAME, NAME_LENGTH);

		// look it up
		wip->muzzle_flash = mflash_lookup(fname);
	}

	// EMP optional stuff (if WIF_EMP is not set, none of this matters, anyway)
	if( optional_string("$EMP Intensity:") ){
		stuff_float(&wip->emp_intensity);
	}
	
	if( optional_string("$EMP Time:") ){
		stuff_float(&wip->emp_time);
	}

	// This is an optional modifier for a weapon that uses the "apply recoil" flag. recoil_force in ship.cpp line 10445 is multiplied by this if defined.
	if (optional_string("$Recoil Modifier:")){
		if (!(wip->wi_flags3 & WIF3_APPLY_RECOIL)){
			Warning(LOCATION, "$Recoil Modifier specified for weapon %s but this weapon does not have the \"apply recoil\" weapon flag set. Automatically setting the flag", wip->name);
			wip->wi_flags3 |= WIF3_APPLY_RECOIL;
		}
		stuff_float(&wip->recoil_modifier);
	}

	// Energy suck optional stuff (if WIF_ENERGY_SUCK is not set, none of this matters anyway)
	if( optional_string("$Leech Weapon:") ){
		stuff_float(&wip->weapon_reduce);
	}

	if( optional_string("$Leech Afterburner:") ){
		stuff_float(&wip->afterburner_reduce);
	}

	if (optional_string("$Corkscrew:"))
	{
		if(optional_string("+Num Fired:")) {
			stuff_int(&wip->cs_num_fired);
		}

		if(optional_string("+Radius:")) {
			stuff_float(&wip->cs_radius);
		}

		if(optional_string("+Fire Delay:")) {
			stuff_int(&wip->cs_delay);
		}
		
		if(optional_string("+Counter rotate:")) {
			stuff_boolean(&wip->cs_crotate);
		}

		if(optional_string("+Twist:")) {
			stuff_float(&wip->cs_twist);
		}
	}

	//electronics tag optional stuff
	//Note that I made all these optional in the interest of modular tables.
	//TODO: Possibly add a warning on first_time define?
	if (optional_string("$Electronics:"))
	{
		if(optional_string("+New Style:")) {
			wip->elec_use_new_style=1;
		}
		else if(optional_string("+Old Style:")) {
			wip->elec_use_new_style=0;
		}

		if(optional_string("+Area Of Effect")) {
			wip->wi_flags3 |= WIF3_AOE_ELECTRONICS;
		}
		
		//New only -WMC
		if(optional_string("+Intensity:")) {
			float temp;
			stuff_float(&temp);
			Warning(LOCATION, "+Intensity is deprecated");
		}

		if(optional_string("+Lifetime:")) {
			stuff_int(&wip->elec_time);
		}

		//New only -WMC
		if(optional_string("+Engine Multiplier:")) {
			stuff_float(&wip->elec_eng_mult);
			if(!wip->elec_use_new_style)Warning(LOCATION, "+Engine multiplier may only be used with new style electronics");
		}

		//New only -WMC
		if(optional_string("+Weapon Multiplier:")) {
			stuff_float(&wip->elec_weap_mult);
			if(!wip->elec_use_new_style)Warning(LOCATION, "+Weapon multiplier may only be used with new style electronics");
		}

		//New only -WMC
		if(optional_string("+Beam Turret Multiplier:")) {
			stuff_float(&wip->elec_beam_mult);
			if(!wip->elec_use_new_style)Warning(LOCATION, "+Beam turret multiplier may only be used with new style electronics");
		}

		//New only -WMC
		if(optional_string("+Sensors Multiplier:")) {
			stuff_float(&wip->elec_sensors_mult);
			if(!wip->elec_use_new_style)Warning(LOCATION, "+Sensors multiplier may only be used with new style electronics");
		}
	
		if(optional_string("+Randomness Time:")) {
			stuff_int(&wip->elec_randomness);
		}
	}

	//read in the spawn angle info
	//if the weapon isn't a spawn weapon, then this is not going to be used.
    int num_spawn_angs_defined = 0;
    float dum_float;

	while (optional_string("$Spawn Angle:"))
    {
        stuff_float(&dum_float);

        if (num_spawn_angs_defined < MAX_SPAWN_TYPES_PER_WEAPON)
        {
            wip->spawn_info[num_spawn_angs_defined].spawn_angle = dum_float;
            num_spawn_angs_defined++;
        }
	}

	if (wip->wi_flags2 & WIF2_LOCAL_SSM && optional_string("$Local SSM:"))
	{
		if(optional_string("+Warpout Delay:")) {
			stuff_int(&wip->lssm_warpout_delay);
		}

		if(optional_string("+Warpin Delay:")) {
			stuff_int(&wip->lssm_warpin_delay);
		}

		if(optional_string("+Stage 5 Velocity:")) {
			stuff_float(&wip->lssm_stage5_vel);
		}

		if(optional_string("+Warpin Radius:")) {
			stuff_float(&wip->lssm_warpin_radius);
		}

		if (optional_string("+Lock Range:")) {
			stuff_float(&wip->lssm_lock_range);
		}
	}

	if (optional_string("$Countermeasure:"))
	{
		if (!(wip->wi_flags & WIF_CMEASURE))
		{
			Warning(LOCATION,"Weapon \'%s\' has countermeasure information defined, but the \"countermeasure\" flag wasn\'t found in the \'$Flags:\' field.\n", wip->name);
		}

		if (optional_string("+Heat Effectiveness:"))
			stuff_float(&wip->cm_heat_effectiveness);

		if (optional_string("+Aspect Effectiveness:"))
			stuff_float(&wip->cm_aspect_effectiveness);

		if (optional_string("+Effective Radius:"))
			stuff_float(&wip->cm_effective_rad);

		if (optional_string("+Missile Detonation Radius:"))
			stuff_float(&wip->cm_detonation_rad);

		if (optional_string("+Single Missile Kill:"))
			stuff_boolean(&wip->cm_kill_single);
	}

	// beam weapon optional stuff
	if ( optional_string("$BeamInfo:") ) {
		// beam type
		if(optional_string("+Type:")) {
			stuff_int(&wip->b_info.beam_type);
		}

		// how long it lasts
		if(optional_string("+Life:")) {
			stuff_float(&wip->b_info.beam_life);
		}

		// warmup time
		if(optional_string("+Warmup:")) {
			stuff_int(&wip->b_info.beam_warmup);
		}

		// warmdowm time
		if(optional_string("+Warmdown:")) {
			stuff_int(&wip->b_info.beam_warmdown);
		}

		// muzzle glow radius
		if(optional_string("+Radius:")) {
			stuff_float(&wip->b_info.beam_muzzle_radius);
		}

		// particle spew count
		if(optional_string("+PCount:")) {
			stuff_int(&wip->b_info.beam_particle_count);
		}

		// particle radius
		if(optional_string("+PRadius:")) {
			stuff_float(&wip->b_info.beam_particle_radius);
		}

		// angle off turret normal
		if(optional_string("+PAngle:")) {
			stuff_float(&wip->b_info.beam_particle_angle);
		}

		// particle bitmap/ani		
		if ( optional_string("+PAni:") ) {
			stuff_string(fname, F_NAME, NAME_LENGTH);
			generic_anim_init(&wip->b_info.beam_particle_ani, fname);
		}

		// magic miss #
		if(optional_string("+Miss Factor:")) {
			for(idx=0; idx<NUM_SKILL_LEVELS; idx++) {
				float temp;
				if(!stuff_float_optional(&temp)) {
					break;
				}
				// an unspecified Miss Factor should apply to all IFFs
				for(iff=0; iff<Num_iffs; iff++) {
					wip->b_info.beam_iff_miss_factor[iff][idx] = temp;
				}
			}
		}
		// now check miss factors for each IFF
		for(iff=0; iff<Num_iffs; iff++) {
			char miss_factor_string[NAME_LENGTH + 15];
			sprintf(miss_factor_string, "+%s Miss Factor:", Iff_info[iff].iff_name);
			if(optional_string(miss_factor_string)) {
				// this Miss Factor applies only to the specified IFF
				for(idx=0; idx<NUM_SKILL_LEVELS; idx++) {
					if(!stuff_float_optional(&wip->b_info.beam_iff_miss_factor[iff][idx])) {
						break;
					}
				}
			}
		}

		// beam fire sound
		parse_sound("+BeamSound:", &wip->b_info.beam_loop_sound, wip->name);

		// warmup sound
		parse_sound("+WarmupSound:", &wip->b_info.beam_warmup_sound, wip->name);

		// warmdown sound
		parse_sound("+WarmdownSound:", &wip->b_info.beam_warmdown_sound, wip->name);

		// glow bitmap
		if (optional_string("+Muzzleglow:") ) {
			stuff_string(fname, F_NAME, NAME_LENGTH);
			generic_anim_init(&wip->b_info.beam_glow, fname);
		}

		if (optional_string("+Directional Glow:")) {
			stuff_float(&wip->b_info.glow_length);
			wip->b_info.directional_glow = true;
		}

		// # of shots (only used for type D beams)
		if(optional_string("+Shots:")) {
			stuff_int(&wip->b_info.beam_shots);
		}

		// make sure that we have at least one shot so that TYPE_D beams will work
		if ( (wip->b_info.beam_type == BEAM_TYPE_D) && (wip->b_info.beam_shots < 1) ) {
			Warning( LOCATION, "Type D beam weapon, '%s', has less than one \"+Shots\" specified!  It must be set to at least 1!!",  wip->name);
			wip->b_info.beam_shots = 1;
		}
		
		// shrinkage
		if(optional_string("+ShrinkFactor:")) {
			stuff_float(&wip->b_info.beam_shrink_factor);
		}
		
		if(optional_string("+ShrinkPct:")) {
			stuff_float(&wip->b_info.beam_shrink_pct);
		}

		if (optional_string("+Range:")) {
			stuff_float(&wip->b_info.range);
		}
		
		if ( optional_string("+Attenuation:") )
			stuff_float(&wip->b_info.damage_threshold);

		if ( optional_string("+BeamWidth:") )
			stuff_float(&wip->b_info.beam_width);

		if ( optional_string("+Beam Flash Effect:") ) {
			stuff_string(fname, F_NAME, NAME_LENGTH);

			if ( VALID_FNAME(fname) )
				wip->flash_impact_weapon_expl_index = Weapon_explosions.Load(fname);
		}
		
		if ( optional_string("+Beam Flash Radius:") )
			stuff_float(&wip->flash_impact_explosion_radius);

		if ( optional_string("+Beam Piercing Effect:") ) {
			stuff_string(fname, F_NAME, NAME_LENGTH);

			if ( VALID_FNAME(fname) )
				wip->piercing_impact_weapon_expl_index = Weapon_explosions.Load(fname);
		}
		
		if ( optional_string("+Beam Piercing Radius:") )
			stuff_float(&wip->piercing_impact_explosion_radius);

		if ( optional_string("+Beam Piercing Effect Velocity:") )
			stuff_float(&wip->piercing_impact_particle_velocity);

		if ( optional_string("+Beam Piercing Splash Effect Velocity:") )
			stuff_float(&wip->piercing_impact_particle_back_velocity);

		if ( optional_string("+Beam Piercing Effect Variance:") )
			stuff_float(&wip->piercing_impact_particle_variance);

		// beam sections
		while ( optional_string("$Section:") ) {
			beam_weapon_section_info *bsip = NULL, tbsw;
			bool nocreate = false, remove = false;
			int bsw_index_override = -1;

			if ( optional_string("+Index:") ) {
				stuff_int(&bsw_index_override);

				if ( optional_string("+remove") ) {
					nocreate = true;
					remove = true;
				}
					
				if ( (bsw_index_override < 0) || (!remove && (bsw_index_override >= wip->b_info.beam_num_sections)) )
					Warning(LOCATION, "Invalid +Index value of %d specified for beam section on weapon '%s'; valid values at this point are %d to %d.", bsw_index_override, wip->name, 0, wip->b_info.beam_num_sections -1);
			}

			if ( optional_string("+nocreate") )
				nocreate = true;

			// Where are we saving data?
			if (bsw_index_override >= 0) {
				if (bsw_index_override < wip->b_info.beam_num_sections) {
					bsip = &wip->b_info.sections[bsw_index_override];
				} else {
					if ( !nocreate ) {
						if ( (bsw_index_override == wip->b_info.beam_num_sections) && (bsw_index_override < MAX_BEAM_SECTIONS) ) {
							bsip = &wip->b_info.sections[wip->b_info.beam_num_sections++];
						} else {
							if ( !remove )
								Warning(LOCATION, "Invalid index for manually-indexed beam section %d (max %d) on weapon %s.", bsw_index_override, MAX_BEAM_SECTIONS, wip->name);

							bsip = &tbsw;
							memset( bsip, 0, sizeof(beam_weapon_section_info) );
							generic_anim_init(&bsip->texture, NULL);
						}
					} else {
						if ( !remove )
							Warning(LOCATION, "Invalid index for manually-indexed beam section %d, and +nocreate specified, on weapon %s", bsw_index_override, wip->name);

						bsip = &tbsw;
						memset( bsip, 0, sizeof(beam_weapon_section_info) );
						generic_anim_init(&bsip->texture, NULL);
					}

				}
			} else {
				if (wip->b_info.beam_num_sections < MAX_BEAM_SECTIONS) {
					bsip = &wip->b_info.sections[wip->b_info.beam_num_sections++];
					generic_anim_init(&bsip->texture, NULL);
				} else {
					Warning(LOCATION, "Too many beam sections for weapon %s - max is %d", wip->name, MAX_BEAM_SECTIONS);
					bsip = &tbsw;
					memset( bsip, 0, sizeof(beam_weapon_section_info) );
					generic_anim_init(&bsip->texture, NULL);
				}
			}
			
			// section width
			if ( optional_string("+Width:") )
				stuff_float(&bsip->width);

			// texture
			if ( optional_string("+Texture:") ) {
				stuff_string(fname, F_NAME, NAME_LENGTH);

				// invisible textures are okay
				if (!stricmp(fname, "invisible")) {
					generic_anim_init(&bsip->texture);
				} else {
					generic_anim_init(&bsip->texture, fname);
				}
			}

			// The E -- Dummied out due to not being used anywhere
			if ( optional_string("+RGBA Inner:") ) {
				ubyte dummy;
				stuff_ubyte(&dummy);
				stuff_ubyte(&dummy);
				stuff_ubyte(&dummy);
				stuff_ubyte(&dummy);
			}

			// The E -- Dummied out due to not being used anywhere
			if ( optional_string("+RGBA Outer:") ) {
				ubyte dummy;
				stuff_ubyte(&dummy);
				stuff_ubyte(&dummy);
				stuff_ubyte(&dummy);
				stuff_ubyte(&dummy);
			}

			// flicker
			if ( optional_string("+Flicker:") ) {
				stuff_float(&bsip->flicker); 
				//Sanity
				if (bsip->flicker < 0.0f || bsip->flicker > 1.0f) {
					mprintf(("WARNING: Invalid value found for +Flicker on section %d of beam %s. Valid range is 0.0 to 1.0, values will be adjusted.\n", wip->b_info.beam_num_sections, wip->name));
					CLAMP(bsip->flicker, 0.0f, 1.0f);
				}
			}

			// zadd
			if ( optional_string("+Zadd:") )
				stuff_float(&bsip->z_add);

 			// beam texture tileing factor -Bobboau
			if ( optional_string("+Tile Factor:") ) {
				stuff_float(&bsip->tile_factor);
				stuff_int(&bsip->tile_type);
			}

			// beam texture moveing stuff -Bobboau
			if ( optional_string("+Translation:") )
				stuff_float(&bsip->translation);

			// if we are actually removing this index then reset it and we'll
			// clean up the entries later
			if (remove) {
				memset( bsip, 0, sizeof(beam_weapon_section_info) );
				generic_anim_init(&bsip->texture, NULL);
			}
		}
	}

	while ( optional_string("$Pspew:") ) {
		int spew_index = -1;
		// check for pspew flag
		if (!( wip->wi_flags & WIF_PARTICLE_SPEW )) {
			Warning(LOCATION, "$Pspew specified for weapon %s but this weapon does not have the \"Particle Spew\" weapon flag set. Automatically setting the flag", wip->name); 
			wip->wi_flags |= WIF_PARTICLE_SPEW;
		}
		// index for xmt edit, replace and remove support
		if (optional_string("+Index:")) {
			stuff_int(&spew_index);
			if (spew_index < 0 || spew_index >= MAX_PARTICLE_SPEWERS) {
				Warning(LOCATION, "+Index in particle spewer out of range. It must be between 0 and %i. Tag will be ignored.", MAX_PARTICLE_SPEWERS);
				spew_index = -1;
			}
		}
		// check for remove flag
		if (optional_string("+Remove")) {
			if (spew_index < 0) {
				Warning(LOCATION, "+Index not specified or is out of range, can not remove spewer.");
			} else { // restore defaults
				wip->particle_spewers[spew_index].particle_spew_type = PSPEW_NONE;
				wip->particle_spewers[spew_index].particle_spew_count = 1;
				wip->particle_spewers[spew_index].particle_spew_time = 25;
				wip->particle_spewers[spew_index].particle_spew_vel = 0.4f;
				wip->particle_spewers[spew_index].particle_spew_radius = 2.0f;
				wip->particle_spewers[spew_index].particle_spew_lifetime = 0.15f;
				wip->particle_spewers[spew_index].particle_spew_scale = 0.8f;
				wip->particle_spewers[spew_index].particle_spew_z_scale = 1.0f;
				wip->particle_spewers[spew_index].particle_spew_rotation_rate = 10.0f;
				wip->particle_spewers[spew_index].particle_spew_offset = vmd_zero_vector;
				wip->particle_spewers[spew_index].particle_spew_velocity = vmd_zero_vector;
				generic_anim_init(&wip->particle_spewers[spew_index].particle_spew_anim, NULL);
			}
		} else { // were not removing the spewer
			if (spew_index < 0) { // index us ether not used or is invalid, so figure out where to put things
				//find a free slot in the pspew info array
				for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) {
					if (wip->particle_spewers[s].particle_spew_type == PSPEW_NONE) {
						spew_index = s;
						break;
					}
				}
			}
			// no empty spot found, the modder tried to define too many spewers, or screwed up the xmts, or my code sucks
			if ( spew_index < 0 ) {
				Warning(LOCATION, "Too many particle spewers, max number of spewers is %i.", MAX_PARTICLE_SPEWERS);
			} else { // we have a valid index, now parse the spewer already
				if (optional_string("+Type:")) { // added type field for pspew types, 0 is the default for reverse compatability -nuke
					char temp_pspew_type[NAME_LENGTH];
					stuff_string(temp_pspew_type, F_NAME, NAME_LENGTH);

					if (!stricmp(temp_pspew_type, NOX("DEFAULT"))) {
						wip->particle_spewers[spew_index].particle_spew_type = PSPEW_DEFAULT;
					} else if (!stricmp(temp_pspew_type, NOX("HELIX"))) {
						wip->particle_spewers[spew_index].particle_spew_type = PSPEW_HELIX;
					} else if (!stricmp(temp_pspew_type, NOX("SPARKLER"))) {	// new types can be added here
						wip->particle_spewers[spew_index].particle_spew_type = PSPEW_SPARKLER;
					} else if (!stricmp(temp_pspew_type, NOX("RING"))) {
						wip->particle_spewers[spew_index].particle_spew_type = PSPEW_RING;
					} else if (!stricmp(temp_pspew_type, NOX("PLUME"))) {
						wip->particle_spewers[spew_index].particle_spew_type = PSPEW_PLUME;
					} else {
						wip->particle_spewers[spew_index].particle_spew_type = PSPEW_DEFAULT;
					}
				// for compatibility with existing tables that don't have a type tag
				} else if (wip->particle_spewers[spew_index].particle_spew_type == PSPEW_NONE) { // make sure the omission of type wasn't to edit an existing entry
					wip->particle_spewers[spew_index].particle_spew_type = PSPEW_DEFAULT;
				}

				if (optional_string("+Count:")) {
					stuff_int(&wip->particle_spewers[spew_index].particle_spew_count);
				}

				if (optional_string("+Time:")) {
					stuff_int(&wip->particle_spewers[spew_index].particle_spew_time);
				}

				if (optional_string("+Vel:")) {
					stuff_float(&wip->particle_spewers[spew_index].particle_spew_vel);
				}

				if (optional_string("+Radius:")) {
					stuff_float(&wip->particle_spewers[spew_index].particle_spew_radius);
				}

				if (optional_string("+Life:")) {
					stuff_float(&wip->particle_spewers[spew_index].particle_spew_lifetime);
				}

				if (optional_string("+Scale:")) {
					stuff_float(&wip->particle_spewers[spew_index].particle_spew_scale);
				}

				if (optional_string("+Z Scale:")) {
					stuff_float(&wip->particle_spewers[spew_index].particle_spew_z_scale);
				}

				if (optional_string("+Rotation Rate:")) {
					stuff_float(&wip->particle_spewers[spew_index].particle_spew_rotation_rate);
				}

				if (optional_string("+Offset:")) {
					stuff_vec3d(&wip->particle_spewers[spew_index].particle_spew_offset);
				}

				if (optional_string("+Initial Velocity:")) {
					stuff_vec3d(&wip->particle_spewers[spew_index].particle_spew_velocity);
				}

				if (optional_string("+Bitmap:")) {
					stuff_string(fname, F_NAME, MAX_FILENAME_LEN);
					generic_anim_init(&wip->particle_spewers[spew_index].particle_spew_anim, fname);
				}
			}
		}	
	}
	// check to see if the pspew flag was enabled but no pspew tags were given, for compatability with retail tables
	if (wip->wi_flags & WIF_PARTICLE_SPEW) {
		bool nospew = true;
		for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++)
			if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE) {
				nospew = false;
			}
		if (nospew) { // set first spewer to default
			wip->particle_spewers[0].particle_spew_type = PSPEW_DEFAULT;
		}
	}

	// tag weapon optional stuff
	if( optional_string("$Tag:")){
		stuff_int(&wip->tag_level);
		stuff_float(&wip->tag_time);		
		wip->wi_flags |= WIF_TAG;
	}	

	if( optional_string("$SSM:")){
		if (stuff_int_optional(&wip->SSM_index) != 2) {
			// We can't make an SSM lookup yet, because weapons are parsed first, but we can save the data to process later. -MageKing17
			stuff_string(fname, F_NAME, NAME_LENGTH);
			delayed_ssm_data temp_data;
			temp_data.filename = filename;
			temp_data.linenum = get_line_num();
			temp_data.ssm_entry = fname;
			if (Delayed_SSM_data.find(wip->name) == Delayed_SSM_data.end())
				Delayed_SSM_names.push_back(wip->name);
			Delayed_SSM_data[wip->name] = temp_data;
		} else {
			// We'll still want to validate the index later. -MageKing17
			delayed_ssm_index_data temp_data;
			temp_data.filename = filename;
			temp_data.linenum = get_line_num();
			if (Delayed_SSM_indices_data.find(wip->name) == Delayed_SSM_indices_data.end())
				Delayed_SSM_indices.push_back(wip->name);
			Delayed_SSM_indices_data[wip->name] = temp_data;
		}
	}// SSM index -Bobboau

	if(optional_string("$FOF:")){
		stuff_float(&wip->field_of_fire);

		if(optional_string("+FOF Spread Rate:")){
			stuff_float(&wip->fof_spread_rate);
			if(required_string("+FOF Reset Rate:")){
				stuff_float(&wip->fof_reset_rate);
			}

			if(required_string("+Max FOF:")){
				float max_fof;
				stuff_float(&max_fof);
				wip->max_fof_spread = max_fof - wip->field_of_fire;

				if (wip->max_fof_spread <= 0.0f) {
					Warning(LOCATION, "WARNING: +Max FOF must be at least as big as $FOF for '%s'! Defaulting to match $FOF, no spread will occur!", wip->name);
					wip->max_fof_spread = 0.0f;
				}
			}
		}
	}


	if( optional_string("$Shots:")){
		stuff_int(&wip->shots);
	}

	//Left in for compatibility
	if ( optional_string("$decal:") ) {
		mprintf(("WARNING: The decal system has been deactivated in FSO builds. Entries for weapon %s will be discarded.\n", wip->name));
		required_string("+texture:");
		stuff_string(fname, F_NAME, NAME_LENGTH);

		if ( optional_string("+backface texture:") ) {
			stuff_string(fname, F_NAME, NAME_LENGTH);
		}

		float bogus;

		required_string("+radius:");
		stuff_float(&bogus);

		if ( optional_string("+burn time:") ) {
			stuff_float(&bogus);
		}
	}


	if (optional_string("$Transparent:")) {
		wip->wi_flags2 |= WIF2_TRANSPARENT;

		required_string("+Alpha:");
		stuff_float(&wip->alpha_max);

		if (wip->alpha_max > 1.0f)
			wip->alpha_max = 1.0f;

		if (wip->alpha_max <= 0.0f) {
			Warning(LOCATION, "WARNING:  Alpha is set to 0 or a negative value for '%s'!  Defaulting to 1.0!", wip->name);
		}

		if (optional_string("+Alpha Min:")) {
			stuff_float(&wip->alpha_min);
            CLAMP(wip->alpha_min, 0.0f, 1.0f);
		}

		if (optional_string("+Alpha Cycle:")) {
			stuff_float(&wip->alpha_cycle);

			if (wip->alpha_max == wip->alpha_min)
				Warning(LOCATION, "WARNING:  Alpha is set to cycle for '%s', but max and min values are the same!", wip->name);
		}
	}

	if (optional_string("$Weapon Hitpoints:")) {
		stuff_int(&wip->weapon_hitpoints);
	} else if (first_time && (wip->wi_flags3 & (WIF3_TURRET_INTERCEPTABLE | WIF3_FIGHTER_INTERCEPTABLE))) {
		wip->weapon_hitpoints = 25;
	}

	// making sure bombs get their hitpoints assigned
	if ((wip->wi_flags & WIF_BOMB) && (wip->weapon_hitpoints == 0)) {
		wip->weapon_hitpoints = 50;
	}

	if(optional_string("$Armor Type:")) {
		stuff_string(buf, F_NAME, WEAPONS_MULTITEXT_LENGTH);
		wip->armor_type_idx = armor_type_get_idx(buf);

		if(wip->armor_type_idx == -1)
			Warning(LOCATION,"Invalid armor name %s specified for weapon %s", buf, wip->name);
	}

	if (optional_string("$Burst Shots:")) {
		stuff_int(&wip->burst_shots);
		if (wip->burst_shots > 0)
			wip->burst_shots--;
	}

	if (optional_string("$Burst Delay:")) {
		int temp;
		stuff_int(&temp);
		if (temp > 0) {
			wip->burst_delay = ((float) temp) / 1000.0f;
		}
	}

	if (optional_string("$Burst Flags:")) {
		parse_string_flag_list((int*)&wip->burst_flags, Burst_fire_flags, Num_burst_fire_flags);
	}

	if (optional_string("$Thruster Flame Effect:")) {
		stuff_string(fname, F_NAME, NAME_LENGTH);

		if (VALID_FNAME(fname))
			generic_anim_init( &wip->thruster_flame, fname );
	}

	if (optional_string("$Thruster Glow Effect:")) {
		stuff_string(fname, F_NAME, NAME_LENGTH);

		if (VALID_FNAME(fname))
			generic_anim_init( &wip->thruster_glow, fname );
	}

	if (optional_string("$Thruster Glow Radius Factor:")) {
		stuff_float(&wip->thruster_glow_factor);
	}

	//pretty stupid if a target must be tagged to shoot tag missiles at it
	if ((wip->wi_flags & WIF_TAG) && (wip->wi_flags2 & WIF2_TAGGED_ONLY))
	{
		Warning(LOCATION, "%s is a tag missile, but the target must be tagged to shoot it", wip->name);
	}

	// if burst delay is longer than firewait skip the whole burst fire option
	if (wip->burst_delay >= wip->fire_wait)
		wip->burst_shots = 0;

	/* Generate a substitution pattern for this weapon.
	This pattern is very naive such that it calculates the lowest common denominator as being all of
	the periods multiplied together.
	*/
	while ( optional_string("$substitute:") ) {
		char subname[NAME_LENGTH];
		int period = 0;
		int index = 0;
		int offset = 0;
		stuff_string(subname, F_NAME, NAME_LENGTH);
		if ( optional_string("+period:") ) {
			stuff_int(&period);
			if ( period <= 0 ) {
				Warning(LOCATION, "Substitution '%s' for weapon '%s' requires a period greater than 0. Setting period to 1.", subname, wip->name);
				period = 1;
			}
			if ( optional_string("+offset:") ) {
				stuff_int(&offset);
				if ( offset <= 0 ) {
					Warning(LOCATION, "Period offset for substitution '%s' of weapon '%s' has to be greater than 0. Setting offset to 1.", subname, wip->name);
					offset = 1;
				}
			}
		} else if ( optional_string("+index:") ) {
			stuff_int(&index);
			if ( index < 0 ) {
				Warning(LOCATION, "Substitution '%s' for weapon '%s' requires an index greater than 0. Setting index to 0.", subname, wip->name);
				index = 0;
			}
		}

		// we are going to use weapon substition so, make sure that the pattern array has at least one element
		if ( wip->num_substitution_patterns == 0 ) {
			// pattern is empty, initialize pattern with the weapon being currently parsed.
			strcpy_s(wip->weapon_substitution_pattern_names[0], wip->name);
			wip->num_substitution_patterns++;
		}

		// if tbler specifies a period then determine if we can fit the resulting pattern
		// neatly into the pattern array.
		if ( period > 0 ) {
			if ( (wip->num_substitution_patterns % period) > 0 ) {
				// not neat, need to expand the pattern so that our frequency pattern fits completly.
				size_t current_size = wip->num_substitution_patterns;
				size_t desired_size = current_size*period;
				if (desired_size > MAX_SUBSTITUTION_PATTERNS) {
					Warning(LOCATION, "The period is too large for the number of substitution patterns!  desired size=" SIZE_T_ARG ", max size=%d", desired_size, MAX_SUBSTITUTION_PATTERNS);
				}
				else {
					wip->num_substitution_patterns = desired_size;

					// now duplicate the current pattern into the new area so the current pattern holds
					for ( size_t i = current_size; i < desired_size; i++ ) {
						strcpy_s(wip->weapon_substitution_pattern_names[i], wip->weapon_substitution_pattern_names[i%current_size]);
					}
				}
			}

			/* Apply the substituted weapon at the requested period, barrel
			shifted by offset if needed.*/
			for ( size_t pos = (period + offset - 1) % period;
				pos < wip->num_substitution_patterns; pos += period )
			{
				strcpy_s(wip->weapon_substitution_pattern_names[pos], subname);
			}
		} else {
			// assume that tbler wanted to specify a index for the new weapon.

			// make sure that there is enough room
			if (index >= MAX_SUBSTITUTION_PATTERNS) {
				Warning(LOCATION, "Substitution pattern index exceeds the maximum size!  Index=%d, max size=%d", index, MAX_SUBSTITUTION_PATTERNS);
			} else {
				if ( (size_t)index >= wip->num_substitution_patterns ) {
					// need to make the pattern bigger by filling the extra with the current weapon.
					for ( size_t i = wip->num_substitution_patterns; i < (size_t)index; i++ ) {
						strcpy_s(wip->weapon_substitution_pattern_names[i], subname);
					}
					wip->num_substitution_patterns = index+1;
				}

				strcpy_s(wip->weapon_substitution_pattern_names[index], subname);
			}
		}
	}

	//Optional score for destroying this weapon.
	if (optional_string("$Score:")) {
		stuff_int(&wip->score);
	}

	return WEAPON_INFO_INDEX(wip);
}

/**
 * For all weapons that spawn weapons, given an index at weaponp->spawn_type,
 * convert the strings in Spawn_names to indices in the Weapon_types array.
 */
void translate_spawn_types()
{
    int	i, j, k;

    for (i = 0; i < Num_weapon_types; i++)
    {
        for (j = 0; j < Weapon_info[i].num_spawn_weapons_defined; j++)
        {
            if ( (Weapon_info[i].spawn_info[j].spawn_type > -1) && (Weapon_info[i].spawn_info[j].spawn_type < Num_spawn_types) )
            {
                int	spawn_type = Weapon_info[i].spawn_info[j].spawn_type;

                Assert( spawn_type < Num_spawn_types );

                for (k = 0; k < Num_weapon_types; k++) 
                {
                    if ( !stricmp(Spawn_names[spawn_type], Weapon_info[k].name) ) 
                    {
                        Weapon_info[i].spawn_info[j].spawn_type = (short)k;

                        if (i == k)
                            Warning(LOCATION, "Weapon %s spawns itself.  Infinite recursion?\n", Weapon_info[i].name);

                        break;
                    }
                }
            }
        }
    }
}

static char Default_cmeasure_name[NAME_LENGTH] = "";

void parse_weaponstbl(const char *filename)
{
	try
	{
		read_file_text(filename, CF_TYPE_TABLES);
		reset_parse();

		if (optional_string("#Primary Weapons"))
		{
			while (required_string_either("#End", "$Name:")) {
				// AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
				if (parse_weapon(WP_LASER, Parsing_modular_table, filename) < 0) {
					continue;
				}
			}
			required_string("#End");
		}

		if (optional_string("#Secondary Weapons"))
		{
			while (required_string_either("#End", "$Name:")) {
				// AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
				if (parse_weapon(WP_MISSILE, Parsing_modular_table, filename) < 0) {
					continue;
				}
			}
			required_string("#End");
		}

		if (optional_string("#Beam Weapons"))
		{
			while (required_string_either("#End", "$Name:")) {
				// AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
				if (parse_weapon(WP_BEAM, Parsing_modular_table, filename) < 0) {
					continue;
				}
			}
			required_string("#End");
		}

		if (optional_string("#Countermeasures"))
		{
			while (required_string_either("#End", "$Name:"))
			{
				int idx = parse_weapon(WP_MISSILE, Parsing_modular_table, filename);

				if (idx < 0) {
					continue;
				}

				//Make sure cmeasure flag is set
				Weapon_info[idx].wi_flags |= WIF_CMEASURE;

				//Set cmeasure index
				if (!strlen(Default_cmeasure_name)) {
					//We can't be sure that index will be the same after sorting, so save the name
					strcpy_s(Default_cmeasure_name, Weapon_info[idx].name);
				}
			}

			required_string("#End");
		}

		// Read in a list of weapon_info indicies that are an ordering of the player weapon precedence.
		// This list is used to select an alternate weapon when a particular weapon is not available
		// during weapon selection.
		if ((!Parsing_modular_table && required_string("$Player Weapon Precedence:")) || optional_string("$Player Weapon Precedence:"))
		{
			Num_player_weapon_precedence = stuff_int_list(Player_weapon_precedence, MAX_WEAPON_TYPES, WEAPON_LIST_TYPE);
		}

		// add tbl/tbm to multiplayer validation list
		fs2netd_add_table_validation(filename);
	}
	catch (const parse::ParseException& e)
	{
		mprintf(("TABLES: Unable to parse '%s'!  Error message = %s.\n", filename, e.what()));
		return;
	}
}

//uses a simple bucket sort to sort weapons, order of importance is:
//Lasers
//Beams
//Child primary weapons
//Fighter missiles and bombs
//Capital missiles and bombs
//Child secondary weapons
void weapon_sort_by_type()
{
	weapon_info *lasers = NULL, *big_lasers = NULL, *beams = NULL, *missiles = NULL, *big_missiles = NULL, *child_primaries = NULL, *child_secondaries = NULL;
	int num_lasers = 0, num_big_lasers = 0, num_beams = 0, num_missiles = 0, num_big_missiles = 0, num_child_primaries = 0, num_child_secondaries = 0;
	int i, weapon_index;

	// get the initial count of each weapon type
	for (i = 0; i < MAX_WEAPON_TYPES; i++) {
		switch (Weapon_info[i].subtype)
		{
			case WP_UNUSED:
				continue;

			case WP_LASER:
				if (Weapon_info[i].wi_flags & WIF_CHILD)
					num_child_primaries++;
				else if (Weapon_info[i].wi_flags & WIF_BIG_ONLY)
					num_big_lasers++;
				else
					num_lasers++;
				break;
		
			case WP_BEAM:
				num_beams++;
				break;

			case WP_MISSILE:
				if (Weapon_info[i].wi_flags & WIF_CHILD)
					num_child_secondaries++;
				else if (Weapon_info[i].wi_flags & WIF_BIG_ONLY)
					num_big_missiles++;
				else
					num_missiles++;
				break;

			default:
				continue;
		}
		
	}

	// allocate the buckets
	if (num_lasers) {
		lasers = new weapon_info[num_lasers];
		Verify( lasers != NULL );
		num_lasers = 0;
	}

	if (num_big_lasers) {
		big_lasers = new weapon_info[num_big_lasers];
		Verify( big_lasers != NULL );
		num_big_lasers = 0;
	}

	if (num_beams) {
		beams = new weapon_info[num_beams];
		Verify( beams != NULL );
		num_beams = 0;
	}

	if (num_missiles) {
		missiles = new weapon_info[num_missiles];
		Verify( missiles != NULL );
		num_missiles = 0;
	}

	if (num_big_missiles) {
		big_missiles = new weapon_info[num_big_missiles];
		Verify( big_missiles != NULL );
		num_big_missiles = 0;
	}

	if (num_child_primaries) {
		child_primaries = new weapon_info[num_child_primaries];
		Verify( child_primaries != NULL );
		num_child_primaries = 0;
	}

	if (num_child_secondaries) {
		child_secondaries = new weapon_info[num_child_secondaries];
		Verify( child_secondaries != NULL );
		num_child_secondaries = 0;
	}

	// fill the buckets
	for (i = 0; i < MAX_WEAPON_TYPES; i++) {
		switch (Weapon_info[i].subtype)
		{
			case WP_UNUSED:
				continue;

			case WP_LASER:
				if (Weapon_info[i].wi_flags & WIF_CHILD)
					child_primaries[num_child_primaries++] = Weapon_info[i];
				else if (Weapon_info[i].wi_flags & WIF_BIG_ONLY)
					big_lasers[num_big_lasers++] = Weapon_info[i];
				else
					lasers[num_lasers++] = Weapon_info[i];
				break;
		
			case WP_BEAM:
				beams[num_beams++] = Weapon_info[i];
				break;

			case WP_MISSILE:
				if (Weapon_info[i].wi_flags & WIF_CHILD)
					child_secondaries[num_child_secondaries++] = Weapon_info[i];
				else if (Weapon_info[i].wi_flags & WIF_BIG_ONLY)
					big_missiles[num_big_missiles++] = Weapon_info[i];
				else
					missiles[num_missiles++]=Weapon_info[i];
				break;

			default:
				continue;
		}
	}

	weapon_index = 0;

	// reorder the weapon_info structure according to our rules defined above
	for (i = 0; i < num_lasers; i++, weapon_index++)
		Weapon_info[weapon_index] = lasers[i];

	for (i = 0; i < num_big_lasers; i++, weapon_index++)
		Weapon_info[weapon_index] = big_lasers[i];

	for (i = 0; i < num_beams; i++, weapon_index++)
		Weapon_info[weapon_index] = beams[i];

	for (i = 0; i < num_child_primaries; i++, weapon_index++)
		Weapon_info[weapon_index] = child_primaries[i];

	// designate start of secondary weapons so that we'll have the correct offset later on
	First_secondary_index = weapon_index;

	for (i = 0; i < num_missiles; i++, weapon_index++)
		Weapon_info[weapon_index] = missiles[i];

	for (i = 0; i < num_big_missiles; i++, weapon_index++)
		Weapon_info[weapon_index] = big_missiles[i];

	for (i = 0; i < num_child_secondaries; i++, weapon_index++)
		Weapon_info[weapon_index] = child_secondaries[i];


	if (lasers)			delete [] lasers;
	if (big_lasers)		delete [] big_lasers;
	if (beams)			delete [] beams;
	if (missiles)		delete [] missiles;
	if (big_missiles)	delete [] big_missiles;
	if (child_primaries)	delete [] child_primaries;
	if (child_secondaries)	delete [] child_secondaries;
}

/**
 * Do any post-parse cleaning on weapon entries
 */
void weapon_clean_entries()
{
	weapon_info *wip;
	int i;

	for (i = 0; i < Num_weapon_types; i++) {
		wip = &Weapon_info[i];

		if (wip->wi_flags & WIF_BEAM) {
			// clean up any beam sections which may have been deleted
			int removed = 0;

			for (int s_idx = 0; s_idx < wip->b_info.beam_num_sections; s_idx++) {
				if ( !strlen(wip->b_info.sections[s_idx].texture.filename) ) {
					int new_idx = s_idx + 1;

					while (new_idx < MAX_BEAM_SECTIONS) {
						memcpy( &wip->b_info.sections[new_idx-1], &wip->b_info.sections[new_idx], sizeof(beam_weapon_section_info) );
						new_idx++;
					}

					removed++;
				}
			}

			if (removed) {
				mprintf(("NOTE: weapon-cleanup is removing %i stale beam sections, out of %i original, from '%s'.\n", removed, wip->b_info.beam_num_sections, wip->name));
				wip->b_info.beam_num_sections -= removed;
			}
		}
	}
}

void weapon_release_bitmaps()
{
	int i, j;
	weapon_info *wip;

	// not for FRED...
	if (Fred_running)
		return;

	// if we are just going to load them all again any, keep everything
	if (Cmdline_load_all_weapons)
		return;

	for (i = 0; i < Num_weapon_types; i++) {
		wip = &Weapon_info[i];

		// go ahead and clear out models, the model paging code will actually take care of
		// releasing this stuff if needed, but we have to keep track of current modelnums ourselves
		if (wip->render_type == WRT_POF)
			wip->model_num = -1;

		// we are only interested in what we don't need for this mission
		if ( used_weapons[i] )
			continue;

		if (wip->render_type == WRT_LASER) {
			if (wip->laser_bitmap.first_frame >= 0) {
				bm_release(wip->laser_bitmap.first_frame);
				wip->laser_bitmap.first_frame = -1;
			}
	
			// now for the glow
			if (wip->laser_glow_bitmap.first_frame >= 0) {
				bm_release(wip->laser_glow_bitmap.first_frame);
				wip->laser_glow_bitmap.first_frame = -1;
			}
		}

		if (wip->wi_flags & WIF_BEAM) {
			// particle animation
			if (wip->b_info.beam_particle_ani.first_frame >= 0) {
				bm_release(wip->b_info.beam_particle_ani.first_frame);
				wip->b_info.beam_particle_ani.first_frame = -1;
			}

			// muzzle glow
			if (wip->b_info.beam_glow.first_frame >= 0) {
				bm_release(wip->b_info.beam_glow.first_frame);
				wip->b_info.beam_glow.first_frame = -1;
			}

			// section textures
			for (j = 0; j < wip->b_info.beam_num_sections; j++) {
				beam_weapon_section_info *bsi = &wip->b_info.sections[j];

				if (bsi->texture.first_frame >= 0) {
					bm_release(bsi->texture.first_frame);
					bsi->texture.first_frame = -1;
				}
			}
		}

		if (wip->wi_flags & WIF_TRAIL) {
			if (wip->tr_info.texture.bitmap_id >= 0) {
				bm_release(wip->tr_info.texture.bitmap_id);
				wip->tr_info.texture.bitmap_id = -1;
			}
		}

		if (wip->wi_flags & WIF_PARTICLE_SPEW) { // tweaked for multiple particle spews -nuke
			for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++)  { // just bitmaps that got loaded
				if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE){
					if (wip->particle_spewers[s].particle_spew_anim.first_frame >= 0) {
						bm_release(wip->particle_spewers[s].particle_spew_anim.first_frame);
						wip->particle_spewers[s].particle_spew_anim.first_frame = -1;
					}
				}
			}
		}

		if (wip->thruster_flame.first_frame >= 0) {
			bm_release(wip->thruster_flame.first_frame);
			wip->thruster_flame.first_frame = -1;
		}

		if (wip->thruster_glow.first_frame >= 0) {
			bm_release(wip->thruster_glow.first_frame);
			wip->thruster_glow.first_frame = -1;
		}
	}
}

bool weapon_is_used(int weapon_index)
{
	Assert( (weapon_index >= 0) || (weapon_index < Num_weapon_types) );
	return (used_weapons[weapon_index] > 0);
}

void weapon_load_bitmaps(int weapon_index)
{
	weapon_info *wip;

	// not for FRED...
	if (Fred_running)
		return;

	if ( (weapon_index < 0) || (weapon_index >= Num_weapon_types) ) {
		Int3();
		return;
	}

	wip = &Weapon_info[weapon_index];

	if ( (wip->render_type == WRT_LASER) && (wip->laser_bitmap.first_frame < 0) ) {
		wip->laser_bitmap.first_frame = bm_load(wip->laser_bitmap.filename);

		if (wip->laser_bitmap.first_frame >= 0) {
			wip->laser_bitmap.num_frames = 1;
			wip->laser_bitmap.total_time = 1;
		}
		// fall back to an animated type
		else if ( generic_anim_load(&wip->laser_bitmap) ) {
			mprintf(("Could not find a usable bitmap for '%s'!\n", wip->name));
			Warning(LOCATION, "Could not find a usable bitmap (%s) for weapon '%s'!\n", wip->laser_bitmap.filename, wip->name);
		}

		// now see if we also have a glow
		if ( strlen(wip->laser_glow_bitmap.filename) ) {
			wip->laser_glow_bitmap.first_frame = bm_load(wip->laser_glow_bitmap.filename);

			if (wip->laser_glow_bitmap.first_frame >= 0) {
				wip->laser_glow_bitmap.num_frames = 1;
				wip->laser_glow_bitmap.total_time = 1;
			}
			// fall back to an animated type
			else if ( generic_anim_load(&wip->laser_glow_bitmap) ) {
				mprintf(("Could not find a usable glow bitmap for '%s'!\n", wip->name));
				Warning(LOCATION, "Could not find a usable glow bitmap (%s) for weapon '%s'!\n", wip->laser_glow_bitmap.filename, wip->name);
			}
		}
	}

	if (wip->wi_flags & WIF_BEAM) {
		// particle animation
		if ( (wip->b_info.beam_particle_ani.first_frame < 0) && strlen(wip->b_info.beam_particle_ani.filename) )
			generic_anim_load(&wip->b_info.beam_particle_ani);

		// muzzle glow
		if ( (wip->b_info.beam_glow.first_frame < 0) && strlen(wip->b_info.beam_glow.filename) ) {
			if ( generic_anim_load(&wip->b_info.beam_glow) ) {
				// animated version failed to load, try static instead
				wip->b_info.beam_glow.first_frame = bm_load(wip->b_info.beam_glow.filename);

				if (wip->b_info.beam_glow.first_frame >= 0) {
					wip->b_info.beam_glow.num_frames = 1;
					wip->b_info.beam_glow.total_time = 1;
				} else {
					mprintf(("Could not find a usable muzzle glow bitmap for '%s'!\n", wip->name));
					Warning(LOCATION, "Could not find a usable muzzle glow bitmap (%s) for weapon '%s'!\n", wip->b_info.beam_glow.filename, wip->name);
				}
			}
		}

		// section textures
		for (int i = 0; i < wip->b_info.beam_num_sections; i++) {
			beam_weapon_section_info *bsi = &wip->b_info.sections[i];

			if ( (bsi->texture.first_frame < 0) && strlen(bsi->texture.filename) ) {
				if ( generic_anim_load(&bsi->texture) ) {
					// animated version failed to load, try static instead
					bsi->texture.first_frame = bm_load(bsi->texture.filename);

					if (bsi->texture.first_frame >= 0) {
						bsi->texture.num_frames = 1;
						bsi->texture.total_time = 1;
					} else {
						mprintf(("Could not find a usable beam section (%i) bitmap for '%s'!\n", i, wip->name));
						Warning(LOCATION, "Could not find a usable beam section (%i) bitmap (%s) for weapon '%s'!\n", i, bsi->texture.filename, wip->name);
					}
				}
			}
		}
	}

	if ( (wip->wi_flags & WIF_TRAIL) && (wip->tr_info.texture.bitmap_id < 0) )
		generic_bitmap_load(&wip->tr_info.texture);

	//WMC - Don't try to load an anim if no anim is specified, Mmkay?
	if (wip->wi_flags & WIF_PARTICLE_SPEW) {
		for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) {	// looperfied for multiple pspewers -nuke
			if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE){

				if ((wip->particle_spewers[s].particle_spew_anim.first_frame < 0) 
					&& (wip->particle_spewers[s].particle_spew_anim.filename[0] != '\0') ) {

					wip->particle_spewers[s].particle_spew_anim.first_frame = bm_load(wip->particle_spewers[s].particle_spew_anim.filename);

					if (wip->particle_spewers[s].particle_spew_anim.first_frame >= 0) {
						wip->particle_spewers[s].particle_spew_anim.num_frames = 1;
						wip->particle_spewers[s].particle_spew_anim.total_time = 1;
					}
					// fall back to an animated type
					else if ( generic_anim_load(&wip->particle_spewers[s].particle_spew_anim) ) {
						mprintf(("Could not find a usable particle spew bitmap for '%s'!\n", wip->name));
						Warning(LOCATION, "Could not find a usable particle spew bitmap (%s) for weapon '%s'!\n", wip->particle_spewers[s].particle_spew_anim.filename, wip->name);
					}
				}
			}
		}
	}

	// load alternate thruster textures
	if (strlen(wip->thruster_flame.filename)) {
		generic_anim_load(&wip->thruster_flame);
	}

	if (strlen(wip->thruster_glow.filename)) {
		wip->thruster_glow.first_frame = bm_load(wip->thruster_glow.filename);
		if (wip->thruster_glow.first_frame >= 0) {
			wip->thruster_glow.num_frames = 1;
			wip->thruster_glow.total_time = 1;
		} else {
			generic_anim_load(&wip->thruster_glow);
		}
	}

	// if this weapon isn't already marked as used, then mark it as such now
	// (this should really only happen if the player is cheating)
	if ( !used_weapons[weapon_index] )
		used_weapons[weapon_index]++;
}

/**
 * Checks all of the weapon infos for substitution patterns and caches the weapon_index of any that it finds. 
 */
void weapon_generate_indexes_for_substitution() {
	for (int i = 0; i < MAX_WEAPON_TYPES; i++) {
		weapon_info *wip = &(Weapon_info[i]);

		if ( wip->num_substitution_patterns > 0 ) {
			for ( size_t j = 0; j < wip->num_substitution_patterns; j++ ) {
				int weapon_index = -1;
				if ( stricmp("none", wip->weapon_substitution_pattern_names[j]) != 0 ) {
					weapon_index = weapon_info_lookup(wip->weapon_substitution_pattern_names[j]);
					if ( weapon_index == -1 ) { // invalid sub weapon
						Warning(LOCATION, "Weapon '%s' requests substitution with '%s' which does not seem to exist",
							wip->name, wip->weapon_substitution_pattern_names[j]);
						continue;
					}
				}

				wip->weapon_substitution_pattern[j] = weapon_index;
			}

			memset(wip->weapon_substitution_pattern_names, 0, sizeof(char) * MAX_SUBSTITUTION_PATTERNS * NAME_LENGTH);
		}
	}
}

void weapon_do_post_parse()
{
	weapon_info *wip;
	int first_cmeasure_index = -1;
	int i;

	weapon_sort_by_type();	// NOTE: This has to be first thing!
	weapon_clean_entries();
	weapon_generate_indexes_for_substitution();

	Default_cmeasure_index = -1;

	// run through weapons list and deal with individual issues
	for (i = 0; i < Num_weapon_types; i++) {
		wip = &Weapon_info[i];

		// set default counter-measure index from the saved name
		if ( (Default_cmeasure_index < 0) && strlen(Default_cmeasure_name) ) {
			if ( !stricmp(wip->name, Default_cmeasure_name) ) {
				Default_cmeasure_index = i;
			}
		}

		// catch a fall back cmeasure index, just in case
		if ( (first_cmeasure_index < 0) && (wip->wi_flags & WIF_CMEASURE) )
			first_cmeasure_index = i;
	}

	// catch cmeasure fallback
	if (Default_cmeasure_index < 0)
		Default_cmeasure_index = first_cmeasure_index;

	// now we want to resolve the countermeasures by species
	for (SCP_vector<species_info>::iterator ii = Species_info.begin(); ii != Species_info.end(); ++ii)
	{
		if (*ii->cmeasure_name)
		{
			int index = weapon_info_lookup(ii->cmeasure_name);
			if (index < 0)
				Warning(LOCATION, "Could not find weapon type '%s' to use as countermeasure on species '%s'", ii->cmeasure_name, ii->species_name);
			else if (Weapon_info[index].wi_flags & WIF_BEAM)
				Warning(LOCATION, "Attempt made to set a beam weapon as a countermeasure on species '%s'", ii->species_name);
			else
				ii->cmeasure_index = index;
		}
	}

	// translate all spawn type weapons to referrnce the appropriate spawned weapon entry
	translate_spawn_types();
}

void weapon_expl_info_init()
{
	int i;

	parse_weapon_expl_tbl("weapon_expl.tbl");

	// check for, and load, modular tables
	parse_modular_table(NOX("*-wxp.tbm"), parse_weapon_expl_tbl);

	// we've got our list so pass it off for final checking and loading
	for (i = 0; i < (int)LOD_checker.size(); i++) {
		Weapon_explosions.Load( LOD_checker[i].filename, LOD_checker[i].num_lods );
	}

	// done
	LOD_checker.clear();
}

void weapon_reset_info()
{
	memset( Weapon_info, 0, sizeof(weapon_info) * MAX_WEAPON_TYPES );

	for (int i = 0; i < MAX_WEAPON_TYPES; i++)
		init_weapon_entry(i);
}

/**
 * This will get called once at game startup
 */
void weapon_init()
{
	if ( !Weapons_inited ) {
		//Init weapon explosion info
		weapon_expl_info_init();

		// parse weapons.tbl
		weapon_reset_info();

		Num_weapon_types = 0;
		Num_spawn_types = 0;

		parse_weaponstbl("weapons.tbl");

		parse_modular_table(NOX("*-wep.tbm"), parse_weaponstbl);

		// do post-parse cleanup
		weapon_do_post_parse();

		Weapons_inited = 1;
	}

	weapon_level_init();
}


/**
 * Call from game_shutdown() only!!
 */
void weapon_close()
{
	int i;

	for (i = 0; i<MAX_WEAPON_TYPES; i++) {
		if (Weapon_info[i].desc) {
			vm_free(Weapon_info[i].desc);
			Weapon_info[i].desc = NULL;
		}

		if (Weapon_info[i].tech_desc) {
			vm_free(Weapon_info[i].tech_desc);
			Weapon_info[i].tech_desc = NULL;
		}
	}

	if (used_weapons != NULL) {
		delete[] used_weapons;
		used_weapons = NULL;
	}

	if (Spawn_names != NULL) {
		for (i=0; i<Num_spawn_types; i++) {
			if (Spawn_names[i] != NULL) {
				vm_free(Spawn_names[i]);
				Spawn_names[i] = NULL;
			}
		}

		vm_free(Spawn_names);
		Spawn_names = NULL;
	}
}

/**
 * This will get called at the start of each level.
 */
void weapon_level_init()
{
	int i;

	// Reset everything between levels
	Num_weapons = 0;
	for (i=0; i<MAX_WEAPONS; i++)	{
		Weapons[i].objnum = -1;
		Weapons[i].weapon_info_index = -1;
	}

	for (i=0; i<MAX_WEAPON_TYPES; i++)	{
		Weapon_info[i].damage_type_idx = Weapon_info[i].damage_type_idx_sav;
		Weapon_info[i].shockwave.damage_type_idx = Weapon_info[i].shockwave.damage_type_idx_sav;
	}

	trail_level_init();		// reset all missile trails

	swarm_level_init();
	missile_obj_list_init();
	
	cscrew_level_init();

	// emp effect
	emp_level_init();

	if (used_weapons == NULL)
		used_weapons = new int[Num_weapon_types];

	// clear out used_weapons between missions
	memset(used_weapons, 0, Num_weapon_types * sizeof(int));

	Weapon_flyby_sound_timer = timestamp(0);
	Weapon_impact_timer = 1;	// inited each level, used to reduce impact sounds
}

MONITOR( NumWeaponsRend )

const float weapon_glow_scale_f = 2.3f;
const float weapon_glow_scale_r = 2.3f;
const float weapon_glow_scale_l = 1.5f;
const int weapon_glow_alpha = 217; // (0.85 * 255);

void weapon_render_DEPRECATED(object *obj)
{
	int num;
	weapon_info *wip;
	weapon *wp;
	color c;

	MONITOR_INC(NumWeaponsRend, 1);

	Assert(obj->type == OBJ_WEAPON);

	num = obj->instance;
	wp = &Weapons[num];
	wip = &Weapon_info[Weapons[num].weapon_info_index];

	if (wip->wi_flags2 & WIF2_TRANSPARENT) {
		if (wp->alpha_current == -1.0f) {
			wp->alpha_current = wip->alpha_max;
		} else if (wip->alpha_cycle > 0.0f) {
			if (wp->alpha_backward) {
				wp->alpha_current += wip->alpha_cycle;

				if (wp->alpha_current > wip->alpha_max) {
					wp->alpha_current = wip->alpha_max;
					wp->alpha_backward = 0;
				}
			} else {
				wp->alpha_current -= wip->alpha_cycle;

				if (wp->alpha_current < wip->alpha_min) {
					wp->alpha_current = wip->alpha_min;
					wp->alpha_backward = 1;
				}
			}
		}
	}

	switch (wip->render_type)
	{
		case WRT_LASER:
		{
			if(wip->laser_length < 0.0001f)
					return;
			// turn off fogging for good measure
			gr_fog_set(GR_FOGMODE_NONE, 0, 0, 0);
			int alpha = 255;
			int framenum = 0;

			if (wip->laser_bitmap.first_frame >= 0) {					
				gr_set_color_fast(&wip->laser_color_1);

				if (wip->laser_bitmap.num_frames > 1) {
					wp->laser_bitmap_frame += flFrametime;

					// Sanity checks
					if (wp->laser_bitmap_frame < 0.0f)
						wp->laser_bitmap_frame = 0.0f;
					if (wp->laser_bitmap_frame > 100.0f)
						wp->laser_bitmap_frame = 0.0f;

					while (wp->laser_bitmap_frame > wip->laser_bitmap.total_time)
						wp->laser_bitmap_frame -= wip->laser_bitmap.total_time;

					framenum = fl2i( (wp->laser_bitmap_frame * wip->laser_bitmap.num_frames) / wip->laser_bitmap.total_time );

					CLAMP(framenum, 0, wip->laser_bitmap.num_frames-1);
				}

				if (wip->wi_flags2 & WIF2_TRANSPARENT)
					alpha = fl2i(wp->alpha_current * 255.0f);

				vec3d headp;
				
				vm_vec_scale_add(&headp, &obj->pos, &obj->orient.vec.fvec, wip->laser_length);
				wp->weapon_flags &= ~WF_CONSIDER_FOR_FLYBY_SOUND;

				if ( batch_add_laser(wip->laser_bitmap.first_frame + framenum, &headp, wip->laser_head_radius, &obj->pos, wip->laser_tail_radius, alpha, alpha, alpha) ) {
					wp->weapon_flags |= WF_CONSIDER_FOR_FLYBY_SOUND;
				}
			}			

			// maybe draw laser glow bitmap
			if (wip->laser_glow_bitmap.first_frame >= 0) {
    			// get the laser color
				weapon_get_laser_color(&c, obj);

                // *Tail point "getting bigger" as well as headpoint isn't being taken into consideration, so
                //  it caused uneven glow between the head and tail, which really shows in big lasers. So...fixed!    -Et1
				vec3d headp2, tailp;

				vm_vec_scale_add(&headp2, &obj->pos, &obj->orient.vec.fvec, wip->laser_length * weapon_glow_scale_l);
                vm_vec_scale_add(&tailp, &obj->pos, &obj->orient.vec.fvec, wip->laser_length * (1 -  weapon_glow_scale_l) );

				framenum = 0;

				if (wip->laser_glow_bitmap.num_frames > 1) {
					wp->laser_glow_bitmap_frame += flFrametime;

					// Sanity checks
					if (wp->laser_glow_bitmap_frame < 0.0f)
						wp->laser_glow_bitmap_frame = 0.0f;
					if (wp->laser_glow_bitmap_frame > 100.0f)
						wp->laser_glow_bitmap_frame = 0.0f;

					while (wp->laser_glow_bitmap_frame > wip->laser_glow_bitmap.total_time)
						wp->laser_glow_bitmap_frame -= wip->laser_glow_bitmap.total_time;

					framenum = fl2i( (wp->laser_glow_bitmap_frame * wip->laser_glow_bitmap.num_frames) / wip->laser_glow_bitmap.total_time );

					CLAMP(framenum, 0, wip->laser_glow_bitmap.num_frames-1);
				}

				if (wip->wi_flags2 & WIF2_TRANSPARENT) {
					alpha = fl2i(wp->alpha_current * 255.0f);
					alpha -= 38; // take 1.5f into account for the normal glow alpha

					if (alpha < 0)
						alpha = 0;
				} else {
					alpha = weapon_glow_alpha;
				}

				batch_add_laser(wip->laser_glow_bitmap.first_frame + framenum, &headp2, wip->laser_head_radius * weapon_glow_scale_f, &tailp, wip->laser_tail_radius * weapon_glow_scale_r, (c.red*alpha)/255, (c.green*alpha)/255, (c.blue*alpha)/255);
			}

			break;
		}

		case WRT_POF:
		{
			uint render_flags = MR_DEPRECATED_NORMAL|MR_DEPRECATED_IS_MISSILE|MR_DEPRECATED_NO_LIGHTING;

			if (Cmdline_missile_lighting && !(wip->wi_flags2 & WIF2_MR_NO_LIGHTING))
				render_flags &= ~MR_DEPRECATED_NO_LIGHTING;

			if (wip->wi_flags2 & WIF2_TRANSPARENT) {
				model_set_alpha(wp->alpha_current);
				render_flags |= MR_DEPRECATED_ALL_XPARENT;
			}

			model_clear_instance(wip->model_num);

			if ( (wip->wi_flags & WIF_THRUSTER) && ((wp->thruster_bitmap > -1) || (wp->thruster_glow_bitmap > -1)) ) {
				float ft;
				mst_info mst;

				//	Add noise to thruster geometry.
				ft = 1.0f;		// Always use 1.0f for missiles					
				ft *= (1.0f + frand()/5.0f - 1.0f/10.0f);
				if (ft > 1.0f)
					ft = 1.0f;

				mst.length.xyz.x = ft;
				mst.length.xyz.y = ft;
				mst.length.xyz.z = ft;

				mst.primary_bitmap = wp->thruster_bitmap;
				mst.primary_glow_bitmap = wp->thruster_glow_bitmap;
				mst.glow_rad_factor = wip->thruster_glow_factor;
				mst.glow_noise = wp->thruster_glow_noise;

				model_set_thrust(wip->model_num, &mst);

				render_flags |= MR_DEPRECATED_SHOW_THRUSTERS;
			}


			//don't render local ssm's when they are still in subspace
			if (wp->lssm_stage==3)
				break;

			int clip_plane=0;
				
			//start a clip plane
			if (wp->lssm_stage==2)
			{
				object *wobj=&Objects[wp->lssm_warp_idx];		//warphole object
				clip_plane=1;

				g3_start_user_clip_plane(&wobj->pos,&wobj->orient.vec.fvec);
			}


			model_render_DEPRECATED(wip->model_num, &obj->orient, &obj->pos, render_flags);
			wp->weapon_flags |= WF_CONSIDER_FOR_FLYBY_SOUND;
			if (clip_plane)
			{
				g3_stop_user_clip_plane();
			}
			break;
		}

		default:
			Warning(LOCATION, "Unknown weapon rendering type = %i for weapon %s\n", wip->render_type, wip->name);
	}
}

void weapon_delete(object *obj)
{
	weapon *wp;
	int num;

	Script_system.SetHookObjects(2, "Weapon", obj, "Self", obj);
	Script_system.RunCondition(CHA_ONWEAPONDELETE);
	Script_system.RemHookVars(2, "Weapon", "Self");

	num = obj->instance;

	Assert( Weapons[num].objnum == OBJ_INDEX(obj));
	wp = &Weapons[num];

	Assert(wp->weapon_info_index >= 0);
	wp->weapon_info_index = -1;
	if (wp->swarm_index >= 0) {
		swarm_delete(wp->swarm_index);
		wp->swarm_index = -1;
	}

	if(wp->cscrew_index >= 0) {
		cscrew_delete(wp->cscrew_index);
		wp->cscrew_index = -1;
	}

	if (wp->missile_list_index >= 0) {
		missle_obj_list_remove(wp->missile_list_index);
		wp->missile_list_index = -1;
	}

	if (wp->trail_ptr != NULL) {
		trail_object_died(wp->trail_ptr);
		wp->trail_ptr = NULL;
	}

	if (wp->hud_in_flight_snd_sig >= 0 && snd_is_playing(wp->hud_in_flight_snd_sig))
		snd_stop(wp->hud_in_flight_snd_sig);

	if (wp->model_instance_num >= 0)
		model_delete_instance(wp->model_instance_num);

	if (wp->cmeasure_ignore_list != nullptr) {
		delete wp->cmeasure_ignore_list;
		wp->cmeasure_ignore_list = nullptr;
	}

	wp->objnum = -1;
	Num_weapons--;
	Assert(Num_weapons >= 0);
}

/**
 * Check if missile is newly locked onto the Player, maybe play a launch warning
 */
void weapon_maybe_play_warning(weapon *wp)
{
	if ( wp->homing_object == Player_obj ) {
		if ( !(wp->weapon_flags & WF_LOCK_WARNING_PLAYED) ) {
			wp->weapon_flags |= WF_LOCK_WARNING_PLAYED;
			// Use heatlock-warning sound for Heat and Javelin for now
			// Possibly add an additional third sound later
			if ( (Weapon_info[wp->weapon_info_index].wi_flags & WIF_HOMING_HEAT) ||
				 (Weapon_info[wp->weapon_info_index].wi_flags & WIF_HOMING_JAVELIN) ) {
				snd_play(&Snds[ship_get_sound(Player_obj, SND_HEATLOCK_WARN)]);
			} else {
				Assert(Weapon_info[wp->weapon_info_index].wi_flags & WIF_HOMING_ASPECT);
				snd_play(&Snds[ship_get_sound(Player_obj, SND_ASPECTLOCK_WARN)]);
			}
		}
	}
}


/**
 * Detonate all missiles near this countermeasure.
 */
void detonate_nearby_missiles(object* killer_objp, object* missile_objp)
{
	if(killer_objp->type != OBJ_WEAPON || missile_objp->type != OBJ_WEAPON) {
		Int3();
		return;
	}

	weapon_info* killer_infop = &Weapon_info[Weapons[killer_objp->instance].weapon_info_index];

	if (killer_infop->cm_kill_single) {
		weapon* wp = &Weapons[missile_objp->instance];
		if (wp->lifeleft > 0.2f) {
			nprintf(("Countermeasures", "Countermeasure (%s-%i) detonated missile (%s-%i) Frame: %i\n",
						killer_infop->name, killer_objp->signature,
						Weapon_info[Weapons[missile_objp->instance].weapon_info_index].name, missile_objp->signature, Framecount));
			wp->lifeleft = 0.2f;
		}
		return;
	}

	missile_obj* mop = GET_FIRST(&Missile_obj_list);

	while(mop != END_OF_LIST(&Missile_obj_list)) {
		object* objp = &Objects[mop->objnum];
		weapon* wp = &Weapons[objp->instance];

		if (iff_x_attacks_y(Weapons[killer_objp->instance].team, wp->team)) {
			if ( Missiontime - wp->creation_time > F1_0/2) {
				if (vm_vec_dist_quick(&killer_objp->pos, &objp->pos) < killer_infop->cm_detonation_rad) {
					if (wp->lifeleft > 0.2f) { 
						nprintf(("Countermeasures", "Countermeasure (%s-%i) detonated missile (%s-%i) Frame: %i\n",
									killer_infop->name, killer_objp->signature,
									Weapon_info[Weapons[objp->instance].weapon_info_index].name, objp->signature, Framecount));
						wp->lifeleft = 0.2f;
					}
				}
			}
		}

		mop = mop->next;
	}
}

/**
 * Find an object for weapon #num (object *weapon_objp) to home on due to heat.
 */
void find_homing_object(object *weapon_objp, int num)
{
	object      *objp, *old_homing_objp;
	weapon_info *wip;
	weapon      *wp;
    ship        *sp;
    ship_info   *sip;
	float       best_dist;
    int         homing_object_team;
    float       dist;
    float       dot;
    vec3d       vec_to_object;
    ship_subsys *target_engines = NULL;

	wp = &Weapons[num];

	wip = &Weapon_info[Weapons[num].weapon_info_index];

	best_dist = 99999.9f;

	// save the old homing object so that multiplayer servers can give the right information
	// to clients if the object changes
	old_homing_objp = wp->homing_object;

	wp->homing_object = &obj_used_list;

	//	Scan all objects, find a weapon to home on.
	for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
		if ((objp->type == OBJ_SHIP) || ((objp->type == OBJ_WEAPON) && (Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags & WIF_CMEASURE)))
		{
			//WMC - Spawn weapons shouldn't go for protected ships
			// ditto for untargeted heat seekers - niffiwan
			if ( (objp->flags & OF_PROTECTED) &&
				((wp->weapon_flags & WF_SPAWNED) || (wip->wi_flags2 & WIF2_UNTARGETED_HEAT_SEEKER)) )
				continue;

			// Spawned weapons should never home in on their parent - even in multiplayer dogfights where they would pass the iff test below
			if ((wp->weapon_flags & WF_SPAWNED) && (objp == &Objects[weapon_objp->parent]))
				continue; 

			homing_object_team = obj_team(objp);
			if (iff_x_attacks_y(wp->team, homing_object_team))
			{
				if ( objp->type == OBJ_SHIP )
                {
                    sp  = &Ships[objp->instance];
                    sip = &Ship_info[sp->ship_info_index];

                    //if the homing weapon is a huge weapon and the ship that is being
                    //looked at is not huge, then don't home
                    if ((wip->wi_flags & WIF_HUGE) &&
                        (sip->flags & (SIF_SMALL_SHIP | SIF_NOT_FLYABLE | SIF_HARMLESS)))
                    {
                        continue;
                    }

					// AL 2-17-98: If ship is immune to sensors, can't home on it (Sandeep says so)!
					if ( sp->flags & SF_HIDDEN_FROM_SENSORS ) {
						continue;
					}

					// Goober5000: if missiles can't home on sensor-ghosted ships,
					// they definitely shouldn't home on stealth ships
					if ( sp->flags2 & SF2_STEALTH && (The_mission.ai_profile->flags & AIPF_FIX_HEAT_SEEKER_STEALTH_BUG) ) {
						continue;
					}

                    if (wip->wi_flags & WIF_HOMING_JAVELIN)
                    {
                        target_engines = ship_get_closest_subsys_in_sight(sp, SUBSYSTEM_ENGINE, &weapon_objp->pos);

                        if (!target_engines)
                            continue;
                    }

					//	MK, 9/4/99.
					//	If this is a player object, make sure there aren't already too many homers.
					//	Only in single player.  In multiplayer, we don't want to restrict it in dogfight on team vs. team.
					//	For co-op, it's probably also OK.
					if (!( Game_mode & GM_MULTIPLAYER )) {
						int	num_homers = compute_num_homing_objects(objp);
						if (The_mission.ai_profile->max_allowed_player_homers[Game_skill_level] < num_homers)
							continue;
					}
				}
                else if (objp->type == OBJ_WEAPON)
				{
                    //don't attempt to home on weapons if the weapon is a huge weapon or is a javelin homing weapon.
                    if (wip->wi_flags & (WIF_HUGE | WIF_HOMING_JAVELIN))
                        continue;
                    
                    //don't look for local ssms that are gone for the time being
					if (Weapons[objp->instance].lssm_stage == 3)
						continue;
				}

				dist = vm_vec_normalized_dir(&vec_to_object, &objp->pos, &weapon_objp->pos);

				if (objp->type == OBJ_WEAPON && (Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags & WIF_CMEASURE)) {
					dist *= 0.5f;
				}

				dot = vm_vec_dot(&vec_to_object, &weapon_objp->orient.vec.fvec);

				if (dot > wip->fov) {
					if (dist < best_dist) {
						best_dist = dist;
						wp->homing_object	= objp;
						wp->target_sig		= objp->signature;
						wp->homing_subsys	= target_engines;

						cmeasure_maybe_alert_success(objp);
					}
				}
			}
		}
	}

	if (wp->homing_object == Player_obj)
		weapon_maybe_play_warning(wp);

	// if the old homing object is different that the new one, send a packet to clients
	if ( MULTIPLAYER_MASTER && (old_homing_objp != wp->homing_object) ) {
		send_homing_weapon_info( num );
	}
}

/**
 * Scan all countermeasures.  Maybe make weapon_objp home on it.
 */
void find_homing_object_cmeasures_1(object *weapon_objp)
{
	object	*objp;
	weapon	*wp, *cm_wp;
	weapon_info	*wip, *cm_wip;
	float		best_dot, dist, dot;

	wp = &Weapons[weapon_objp->instance];
	wip = &Weapon_info[wp->weapon_info_index];

	best_dot = wip->fov;			//	Note, setting to this avoids comparison below.

	for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) )
	{
		//first check if its a weapon, then setup the pointers
		if (objp->type == OBJ_WEAPON)
		{
			cm_wp = &Weapons[objp->instance];
			cm_wip = &Weapon_info[cm_wp->weapon_info_index];

			if (cm_wip->wi_flags & WIF_CMEASURE)
			{
				//don't have a weapon try to home in on itself
				if (objp==weapon_objp)
					continue;

				//don't have a weapon try to home in on missiles fired by the same team, unless its the traitor team.
				if ((wp->team == cm_wp->team) && (wp->team != Iff_traitor))
					continue;

				vec3d	vec_to_object;
				dist = vm_vec_normalized_dir(&vec_to_object, &objp->pos, &weapon_objp->pos);

				if (dist < cm_wip->cm_effective_rad)
				{
					float chance;

					if (wp->cmeasure_ignore_list == nullptr) {
						wp->cmeasure_ignore_list = new SCP_vector<int>;
					}
					else {
						bool found = false;
						for (auto ii = wp->cmeasure_ignore_list->cbegin(); ii != wp->cmeasure_ignore_list->cend(); ++ii) {
							if (objp->signature == *ii) {
								nprintf(("CounterMeasures", "Weapon (%s-%04i) already seen CounterMeasure (%s-%04i) Frame: %i\n",
											wip->name, weapon_objp->instance, cm_wip->name, objp->signature, Framecount));
								found = true;
								break;
							}
						}
						if (found) {
							continue;
						}
					}

					if (wip->wi_flags & WIF_HOMING_ASPECT) {
						// aspect seeker this likely to chase a countermeasure
						chance = cm_wip->cm_aspect_effectiveness/wip->seeker_strength;
					} else {
						// heat seeker and javelin HS this likely to chase a countermeasure
						chance = cm_wip->cm_heat_effectiveness/wip->seeker_strength;
					}

					// remember this cmeasure so it can be ignored in future
					wp->cmeasure_ignore_list->push_back(objp->signature);

					if (frand() >= chance) {
						// failed to decoy
						nprintf(("CounterMeasures", "Weapon (%s-%04i) ignoring CounterMeasure (%s-%04i) Frame: %i\n",
									wip->name, weapon_objp->instance, cm_wip->name, objp->signature, Framecount));
					}
					else {
						// successful decoy, maybe chase the new cm
						dot = vm_vec_dot(&vec_to_object, &weapon_objp->orient.vec.fvec);

						if (dot > best_dot)
						{
							best_dot = dot;
							wp->homing_object = objp;
							cmeasure_maybe_alert_success(objp);
							nprintf(("CounterMeasures", "Weapon (%s-%04i) chasing CounterMeasure (%s-%04i) Frame: %i\n",
										wip->name, weapon_objp->instance, cm_wip->name, objp->signature, Framecount));
						}
					}
				}
			}
		}
	}
}


/**
 * Someone launched countermeasures.
 * For all heat-seeking homing objects, see if should favor tracking a countermeasure instead.
 */
void find_homing_object_cmeasures()
{
	object	*weapon_objp;

	if (Cmeasures_homing_check == 0)
		return;

	if (Cmeasures_homing_check <= 0)
		Cmeasures_homing_check = 1;

	Cmeasures_homing_check--;

	for (weapon_objp = GET_FIRST(&obj_used_list); weapon_objp != END_OF_LIST(&obj_used_list); weapon_objp = GET_NEXT(weapon_objp) ) {
		if (weapon_objp->type == OBJ_WEAPON) {
			weapon_info	*wip = &Weapon_info[Weapons[weapon_objp->instance].weapon_info_index];

			if (wip->wi_flags & WIF_HOMING)
				find_homing_object_cmeasures_1(weapon_objp);
		}
	}

}

/**
 * Find object with signature "sig" and make weapon home on it.
 */
void find_homing_object_by_sig(object *weapon_objp, int sig)
{
	ship_obj		*sop;
	weapon		*wp;
	object		*old_homing_objp;

	wp = &Weapons[weapon_objp->instance];

	// save the old object so that multiplayer masters know whether to send a homing update packet
	old_homing_objp = wp->homing_object;

	sop = GET_FIRST(&Ship_obj_list);
	while(sop != END_OF_LIST(&Ship_obj_list)) {
		object	*objp;

		objp = &Objects[sop->objnum];
		if (objp->signature == sig) {
			wp->homing_object = objp;
			wp->target_sig = objp->signature;
			break;
		}

		sop = sop->next;
	}

	// if the old homing object is different that the new one, send a packet to clients
	if ( MULTIPLAYER_MASTER && (old_homing_objp != wp->homing_object) ) {
		send_homing_weapon_info( weapon_objp->instance );
	}
}

bool aspect_should_lose_target(weapon* wp)
{
	Assert(wp != NULL);
	
	if (wp->homing_object->signature != wp->target_sig) {
		if (wp->homing_object->type == OBJ_WEAPON)
		{
			weapon_info* target_info = &Weapon_info[Weapons[wp->homing_object->instance].weapon_info_index];

			if (target_info->wi_flags & WIF_CMEASURE)
			{
				// Check if we can home on this countermeasure
				bool home_on_cmeasure = The_mission.ai_profile->flags2 & AIPF2_ASPECT_LOCK_COUNTERMEASURE
					|| target_info->wi_flags3 & WIF3_CMEASURE_ASPECT_HOME_ON;

				if (!home_on_cmeasure)
				{
					return true;
				}
			}
		}
	}

	return false;
}

/**
 * Make weapon num home.  It's also object *obj.
 */
void weapon_home(object *obj, int num, float frame_time)
{
	weapon		*wp;
	weapon_info	*wip;
	object		*hobjp;

	Assert(obj->type == OBJ_WEAPON);
	Assert(obj->instance == num);
	wp = &Weapons[num];
	wip = &Weapon_info[wp->weapon_info_index];
	hobjp = Weapons[num].homing_object;

	//local ssms home only in stages 1 and 5
	if ( (wp->lssm_stage==2) || (wp->lssm_stage==3) || (wp->lssm_stage==4))
		return;

	float max_speed;

	if ((wip->wi_flags2 & WIF2_LOCAL_SSM) && (wp->lssm_stage==5))
		max_speed=wip->lssm_stage5_vel;
	else
		max_speed=wip->max_speed;

	//	If not [free-flight-time] gone by, don't home yet.
	// Goober5000 - this has been fixed back to more closely follow the original logic.  Remember, the retail code
	// had 0.5 second of free flight time, the first half of which was spent ramping up to full speed.
	if ((hobjp == &obj_used_list) || ( f2fl(Missiontime - wp->creation_time) < (wip->free_flight_time / 2) )) {
		if (f2fl(Missiontime - wp->creation_time) > wip->free_flight_time) {
			// If this is a heat seeking homing missile and [free-flight-time] has elapsed since firing
			// and we don't have a target (else we wouldn't be inside the IF), find a new target.
			if (wip->wi_flags & WIF_HOMING_HEAT) {
				find_homing_object(obj, num);
			}
			// modders may want aspect homing missiles to die if they lose their target
			else if (wip->wi_flags & WIF_LOCKED_HOMING && wip->wi_flags3 & WIF3_DIE_ON_LOST_LOCK) {
				if (wp->lifeleft > 0.5f) {
					wp->lifeleft = frand_range(0.1f, 0.5f); // randomise a bit to avoid multiple missiles detonating in one frame
				}
				return;
			}
		}
		else if (MULTIPLAYER_MASTER && (wip->wi_flags & WIF_LOCKED_HOMING) && (wp->weapon_flags & WF_HOMING_UPDATE_NEEDED)) {
			wp->weapon_flags &= ~WF_HOMING_UPDATE_NEEDED; 
			send_homing_weapon_info(num);
		}

		if (wip->acceleration_time > 0.0f) {
			if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
				float t;

				t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
				obj->phys_info.speed = wp->launch_speed + MAX(0.0f, wp->weapon_max_vel - wp->launch_speed) * t;
			}
		}
		// since free_flight_time can now be 0, guard against that
		else if (wip->free_flight_time > 0.0f) {
			if (obj->phys_info.speed > max_speed) {
				obj->phys_info.speed -= frame_time * (2 / wip->free_flight_time);
			} else if ((obj->phys_info.speed < max_speed / (2 / wip->free_flight_time)) && (wip->wi_flags & WIF_HOMING_HEAT)) {
				obj->phys_info.speed = max_speed / (2 / wip->free_flight_time);
			}
		}
		// no free_flight_time, so immediately set desired speed
		else {
			obj->phys_info.speed = max_speed;
		}

		// set velocity using whatever speed we have
		vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);

		return;
	}

	// if we've got this far, this should be valid
	weapon_info* hobj_infop = &Weapon_info[Weapons[hobjp->instance].weapon_info_index];

	if (wip->acceleration_time > 0.0f) {
		if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
			float t;

			t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
			obj->phys_info.speed = wp->launch_speed + (wp->weapon_max_vel - wp->launch_speed) * t;
			vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);
		}
	}

	// AL 4-8-98: If original target for aspect lock missile is lost, stop homing
	// WCS - or javelin
	if (wip->wi_flags & WIF_LOCKED_HOMING) {
		if ( wp->target_sig > 0 ) {
			if (aspect_should_lose_target(wp))
			{ 
				wp->homing_object = &obj_used_list;
				return;
			}
		}
	}

  	// AL 4-13-98: Stop homing on a subsystem if parent ship has changed
	if (wip->wi_flags & WIF_HOMING_HEAT) {
		if ( wp->target_sig > 0 ) {
			if ( wp->homing_object->signature != wp->target_sig ) {
				wp->homing_subsys = NULL;
			}
		}
	}

	// If target subsys is dead make missile pick random spot on target as attack point.
	if (wp->homing_subsys != NULL) {
		if (wp->homing_subsys->flags & SSF_MISSILES_IGNORE_IF_DEAD) {
			if ((wp->homing_subsys->max_hits > 0) && (wp->homing_subsys->current_hits <= 0)) {
				wp->homing_object = &obj_used_list;
				return;
			}
		}
	}

	// Make sure Javelin HS missiles always home on engine subsystems if ships
	if ((wip->wi_flags & WIF_HOMING_JAVELIN) &&
		(hobjp->type == OBJ_SHIP) &&
		(wp->target_sig > 0) &&
		(wp->homing_subsys != NULL) &&
		(wp->homing_subsys->system_info->type != SUBSYSTEM_ENGINE)) {
			int sindex = ship_get_by_signature(wp->target_sig);
			if (sindex >= 0) {
				ship *enemy = &Ships[sindex];
				wp->homing_subsys = ship_get_closest_subsys_in_sight(enemy, SUBSYSTEM_ENGINE, &Objects[wp->objnum].pos);
			}
	}

	// If Javelin HS missile doesn't home in on a subsystem but homing in on a
	// ship, lose lock alltogether
	// Javelins can only home in one Engines or bombs.
	if ((wip->wi_flags & WIF_HOMING_JAVELIN) &&
		(hobjp->type == OBJ_SHIP) &&
		(wp->target_sig > 0) &&
		(wp->homing_subsys == NULL)) {
			wp->homing_object = &obj_used_list;
			return;
	}

	// TODO maybe add flag to allow WF_LOCKED_HOMING to lose target when target is dead

	switch (hobjp->type) {
	case OBJ_NONE:
		if (wip->wi_flags & WIF_LOCKED_HOMING) {
			find_homing_object_by_sig(obj, wp->target_sig);
		}
		else {
			find_homing_object(obj, num);
		}
		return;
		break;
	case OBJ_SHIP:
		if (hobjp->signature != wp->target_sig) {
			if (wip->wi_flags & WIF_LOCKED_HOMING) {
				find_homing_object_by_sig(obj, wp->target_sig);
			}
			else {
				find_homing_object(obj, num);
			}
			return;
		}
		break;
	case OBJ_WEAPON:
	{
		bool home_on_cmeasure = The_mission.ai_profile->flags2 & AIPF2_ASPECT_LOCK_COUNTERMEASURE
			|| hobj_infop->wi_flags3 & WIF3_CMEASURE_ASPECT_HOME_ON;

		// don't home on countermeasures or non-bombs, that's handled elsewhere
		if (((hobj_infop->wi_flags & WIF_CMEASURE) && !home_on_cmeasure))
		{
			break;
		}
		else if (!(hobj_infop->wi_flags & WIF_BOMB))
		{
			break;
		}

		if (wip->wi_flags & WIF_LOCKED_HOMING) {
			find_homing_object_by_sig(obj, wp->target_sig);
		}
		else {
			find_homing_object(obj, num);
		}
		break;
	}
	default:
		return;
	}

	//	See if this weapon is the nearest homing object to the object it is homing on.
	//	If so, update some fields in the target object's ai_info.
	if (hobjp != &obj_used_list) {
		float	dist;

		dist = vm_vec_dist_quick(&obj->pos, &hobjp->pos);

		if (hobjp->type == OBJ_SHIP) {
			ai_info	*aip;

			aip = &Ai_info[Ships[hobjp->instance].ai_index];

			if ((aip->nearest_locked_object == -1) || (dist < aip->nearest_locked_distance)) {
				aip->nearest_locked_object = obj-Objects;
				aip->nearest_locked_distance = dist;
			}
		}
	}

	//	If the object it is homing on is still valid, home some more!
	if (hobjp != &obj_used_list) {
		float		old_dot, vel;
		vec3d	vec_to_goal;
		vec3d	target_pos;	// position of what the homing missile is seeking

		vm_vec_zero(&target_pos);

		// the homing missile may be seeking a subsystem on a ship.  If so, we need to calculate the
		// world coordinates of that subsystem so the homing missile can seek it out.
		//	For now, March 7, 1997, MK, heat seeking homing missiles will be able to home on
		//	any subsystem.  Probably makes sense for them to only home on certain kinds of subsystems.
		if ( (wp->homing_subsys != NULL) && !(wip->wi_flags2 & WIF2_NON_SUBSYS_HOMING) && hobjp->type == OBJ_SHIP) {
			get_subsystem_world_pos(hobjp, Weapons[num].homing_subsys, &target_pos);
			wp->homing_pos = target_pos;	// store the homing position in weapon data
			Assert( !vm_is_vec_nan(&wp->homing_pos) );
		} else {
			float	fov;
			float	dist;

			dist = vm_vec_dist_quick(&obj->pos, &hobjp->pos);
			if (hobjp->type == OBJ_WEAPON && (hobj_infop->wi_flags & WIF_CMEASURE))
			{
				if (dist < hobj_infop->cm_detonation_rad)
				{
					//	Make this missile detonate soon.  Not right away, not sure why.  Seems better.
					if (iff_x_attacks_y(Weapons[hobjp->instance].team, wp->team)) {
						detonate_nearby_missiles(hobjp, obj);
						return;
					}
				}
			}

			fov = -1.0f;

			int pick_homing_point = 0;
			if ( IS_VEC_NULL(&wp->homing_pos) ) {
				pick_homing_point = 1;
			}

			//	Update homing position if it hasn't been set, you're within 500 meters, or every half second, approximately.
			//	For large objects, don't lead them.
			if (hobjp->radius < 40.0f) {
				target_pos = hobjp->pos;
				wp->homing_pos = target_pos;
			} else if ( pick_homing_point || (dist < 500.0f) || (rand_chance(flFrametime, 2.0f)) ) {

				if (hobjp->type == OBJ_SHIP) {
					if ( !pick_homing_point ) {
						// ensure that current attack point is only updated in world coords (ie not pick a different vertex)
						wp->pick_big_attack_point_timestamp = 0;
					}

					if ( pick_homing_point && !(wip->wi_flags2 & WIF2_NON_SUBSYS_HOMING) ) {
						// If *any* player is parent of homing missile, then use position where lock indicator is
						if ( Objects[obj->parent].flags & OF_PLAYER_SHIP ) {
							player *pp;

							// determine the player
							pp = Player;

							if ( Game_mode & GM_MULTIPLAYER ) {
								int pnum;

								pnum = multi_find_player_by_object( &Objects[obj->parent] );
								if ( pnum != -1 ){
									pp = Net_players[pnum].m_player;
								}
							}

							// If player has apect lock, we don't want to find a homing point on the closest
							// octant... setting the timestamp to 0 ensures this.
							if (wip->wi_flags & WIF_LOCKED_HOMING) {
								wp->pick_big_attack_point_timestamp = 0;
							} else {
								wp->pick_big_attack_point_timestamp = 1;
							}

							if ( pp && pp->locking_subsys ) {
								wp->big_attack_point = pp->locking_subsys->system_info->pnt;
							} else {
								vm_vec_zero(&wp->big_attack_point);
							}
						}
					}

					ai_big_pick_attack_point(hobjp, obj, &target_pos, fov);

				} else {
					target_pos = hobjp->pos;
				}

				wp->homing_pos = target_pos;
				Assert( !vm_is_vec_nan(&wp->homing_pos) );
			} else
				target_pos = wp->homing_pos;
		}

		//	Couldn't find a lock.
		if (IS_VEC_NULL(&target_pos))
			return;

		//	Cause aspect seeking weapon to home at target's predicted position.
		//	But don't use predicted position if dot product small or negative.
		//	If do this, with a ship headed towards missile, could choose a point behind missile.
		float	dist_to_target, time_to_target;
		
		dist_to_target = vm_vec_normalized_dir(&vec_to_goal, &target_pos, &obj->pos);
		time_to_target = dist_to_target/max_speed;

		vec3d	tvec;
		tvec = obj->phys_info.vel;
		vm_vec_normalize(&tvec);

		old_dot = vm_vec_dot(&tvec, &vec_to_goal);

		//	If a weapon has missed its target, detonate it.
		//	This solves the problem of a weapon circling the center of a subsystem that has been blown away.
		//	Problem: It does not do impact damage, just proximity damage.
		if ((dist_to_target < flFrametime * obj->phys_info.speed * 4.0f + 10.0f) &&
            (old_dot < wip->fov) &&
            (wp->lifeleft > 0.01f) &&
            (wp->homing_object != &obj_used_list) &&
            (wp->homing_object->type == OBJ_SHIP))
        {
            wp->lifeleft = 0.01f;
        }

		//	Only lead target if more than one second away.  Otherwise can miss target.  I think this
		//	is what's causing Harbingers to miss the super destroyer. -- MK, 4/15/98
		if ((old_dot > 0.1f) && (time_to_target > 0.1f)) {
			if (wip->wi_flags2 & WIF2_VARIABLE_LEAD_HOMING) {
				vm_vec_scale_add2(&target_pos, &hobjp->phys_info.vel, (0.33f * wip->target_lead_scaler * MIN(time_to_target, 6.0f)));
			} else if (wip->wi_flags & WIF_LOCKED_HOMING) {
				vm_vec_scale_add2(&target_pos, &hobjp->phys_info.vel, MIN(time_to_target, 2.0f));
			}
		}

		//	If a HEAT seeking (rather than ASPECT seeking) homing missile, verify that target is in viewcone.
		if (wip->wi_flags & WIF_HOMING_HEAT) {
			if ((old_dot < wip->fov) && (dist_to_target > wip->shockwave.inner_rad*1.1f)) {	//	Delay finding new target one frame to allow detonation.
				find_homing_object(obj, num);
				return;			//	Maybe found a new homing object.  Return, process more next frame.
			} else	//	Subtract out life based on how far from target this missile points.
				if ((wip->fov < 0.95f) && !(wip->wi_flags2 & WIF2_NO_LIFE_LOST_IF_MISSED)) {
					wp->lifeleft -= flFrametime * (0.95f - old_dot);
				}
		} else if (wip->wi_flags & WIF_LOCKED_HOMING) {	//	subtract life as if max turn is 90 degrees.
			if ((wip->fov < 0.95f) && !(wip->wi_flags2 & WIF2_NO_LIFE_LOST_IF_MISSED))
				wp->lifeleft -= flFrametime * (0.95f - old_dot);
		} else {
			Warning(LOCATION, "Tried to make weapon '%s' home, but found it wasn't aspect-seeking or heat-seeking or a Javelin!", wip->name);
		}


		//	Control speed based on dot product to goal.  If close to straight ahead, move
		//	at max speed, else move slower based on how far from ahead.
		if (old_dot < 0.90f) {
			obj->phys_info.speed = MAX(0.2f, old_dot* (float) fabs(old_dot));
			if (obj->phys_info.speed < max_speed*0.75f)
				obj->phys_info.speed = max_speed*0.75f;
		} else
			obj->phys_info.speed = max_speed;


		if (wip->acceleration_time > 0.0f) {
			// Ramp up speed linearly for the given duration
			if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
				float t;

				t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
				obj->phys_info.speed = wp->launch_speed + MAX(0.0f, wp->weapon_max_vel - wp->launch_speed) * t;
			}
		} else if (!(wip->wi_flags3 & WIF3_NO_HOMING_SPEED_RAMP) && Missiontime - wp->creation_time < i2f(1)) {
			// Default behavior:
			// For first second of weapon's life, it doesn't fly at top speed.  It ramps up.
			float	t;

			t = f2fl(Missiontime - wp->creation_time);
			obj->phys_info.speed *= t*t;
		}

		Assert( obj->phys_info.speed > 0.0f );

		vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);

		// turn the missile towards the target only if non-swarm.  Homing swarm missiles choose
		// a different vector to turn towards, this is done in swarm_update_direction().
		if ( wp->swarm_index < 0 ) {
			ai_turn_towards_vector(&target_pos, obj, frame_time, wip->turn_time, NULL, NULL, 0.0f, 0, NULL);
			vel = vm_vec_mag(&obj->phys_info.desired_vel);

			vm_vec_copy_scale(&obj->phys_info.desired_vel, &obj->orient.vec.fvec, vel);

		}
	}
}

// as Mike K did with ships -- break weapon into process_pre and process_post for code to execute
// before and after physics movement

void weapon_process_pre( object *obj, float frame_time)
{
	if(obj->type != OBJ_WEAPON)
		return;

	weapon *wp = &Weapons[obj->instance];
	weapon_info *wip = &Weapon_info[wp->weapon_info_index];

	// if the object is a corkscrew style weapon, process it now
	if((obj->type == OBJ_WEAPON) && (Weapons[obj->instance].cscrew_index >= 0)){
		cscrew_process_pre(obj);
	}

	//WMC - Originally flak_maybe_detonate, moved here.
	if(wp->det_range > 0.0f)
	{
		vec3d temp;
		vm_vec_sub(&temp, &obj->pos, &wp->start_pos);
		if(vm_vec_mag(&temp) >= wp->det_range){
			weapon_detonate(obj);		
		}
	}

	//WMC - Maybe detonate weapon anyway!
	if(wip->det_radius > 0.0f)
	{
		if((wp->homing_object != &obj_used_list) && (wp->homing_object->type != 0))
		{
			if(!IS_VEC_NULL(&wp->homing_pos) && vm_vec_dist(&wp->homing_pos, &obj->pos) <= wip->det_radius)
			{
				weapon_detonate(obj);
			}
		} else if(wp->target_num > -1)
		{
			if(vm_vec_dist(&obj->pos, &Objects[wp->target_num].pos) <= wip->det_radius)
			{
				weapon_detonate(obj);
			}
		}
	}
}

int	Homing_hits = 0, Homing_misses = 0;


MONITOR( NumWeapons )

/**
 * Maybe play a "whizz sound" if close enough to view position
 */
void weapon_maybe_play_flyby_sound(object *weapon_objp, weapon *wp)
{
	// don't play flyby sounds too close together
	if ( !timestamp_elapsed(Weapon_flyby_sound_timer) ) {
		return;
	}

	if ( !(wp->weapon_flags & WF_PLAYED_FLYBY_SOUND) && (wp->weapon_flags & WF_CONSIDER_FOR_FLYBY_SOUND) ) {
		float		dist, dot, radius;

		if ( (Weapon_info[wp->weapon_info_index].wi_flags & WIF_CORKSCREW) ) {
			dist = vm_vec_dist_quick(&weapon_objp->last_pos, &Eye_position);
		} else {
			dist = vm_vec_dist_quick(&weapon_objp->pos, &Eye_position);
		}

		if ( Viewer_obj ) {
			radius = Viewer_obj->radius;
		} else {
			radius = 0.0f;
		}

		if ( (dist > radius) && (dist < 55) ) {
			vec3d	vec_to_weapon;

			vm_vec_sub(&vec_to_weapon, &weapon_objp->pos, &Eye_position);
			vm_vec_normalize(&vec_to_weapon);

			// ensure laser is in front of eye
			dot = vm_vec_dot(&vec_to_weapon, &Eye_matrix.vec.fvec);
			if ( dot < 0.1 ) {
				return;
			}

			// ensure that laser is moving in similar direction to fvec
			dot = vm_vec_dot(&vec_to_weapon, &weapon_objp->orient.vec.fvec);
			
			if ( (dot < -0.80) && (dot > -0.98) ) {
				if(Weapon_info[wp->weapon_info_index].flyby_snd != -1) {
					snd_play_3d( &Snds[Weapon_info[wp->weapon_info_index].flyby_snd], &weapon_objp->pos, &Eye_position );
				} else {
					if ( Weapon_info[wp->weapon_info_index].subtype == WP_LASER ) {
						snd_play_3d( &Snds[SND_WEAPON_FLYBY], &weapon_objp->pos, &Eye_position );
					}
				}
				Weapon_flyby_sound_timer = timestamp(200);
				wp->weapon_flags |= WF_PLAYED_FLYBY_SOUND;
			}
		}
	}
}

// process a weapon after physics movement.  MWA reorders some of the code on 8/13 for multiplayer.  When
// adding something to this function, decide whether or not a client in a multiplayer game needs to do
// what is normally done in a single player game.  Things like plotting an object on a radar, effect
// for exhaust are things that are done on all machines.  Things which calculate weapon targets, new
// velocities, etc, are server only functions and should go after the if ( !MULTIPLAYER_MASTER ) statement
// See Allender if you cannot decide what to do.
void weapon_process_post(object * obj, float frame_time)
{
	int			num;	
	weapon_info	*wip;
	weapon		*wp;

	MONITOR_INC( NumWeapons, 1 );	
	
	Assert(obj->type == OBJ_WEAPON);

	num = obj->instance;

#ifndef NDEBUG
	int objnum;
	objnum = OBJ_INDEX(obj);
	Assert( Weapons[num].objnum == objnum );
#endif

	wp = &Weapons[num];

	wp->lifeleft -= frame_time;

	wip = &Weapon_info[wp->weapon_info_index];

	
	if (wip->wi_flags2 & WIF2_LOCAL_SSM)
	{
		if ((wp->lssm_stage != 5) && (wp->lssm_stage != 0))
		{
			wp->lifeleft += frame_time;
		}
	}


	// check life left.  Multiplayer client code will go through here as well.  We must be careful in weapon_hit
	// when killing a missile that spawn child weapons!!!!
	if ( wp->lifeleft < 0.0f ) {
		if ( wip->subtype & WP_MISSILE ) {
			if(Game_mode & GM_MULTIPLAYER){				
				if ( !MULTIPLAYER_CLIENT || (MULTIPLAYER_CLIENT && (wp->lifeleft < -2.0f)) || (MULTIPLAYER_CLIENT && (wip->wi_flags & WIF_CHILD))) {					// don't call this function multiplayer client -- host will send this packet to us
					weapon_detonate(obj);					
				}
			} else {
				weapon_detonate(obj);									
			}
			if (wip->wi_flags & WIF_HOMING) {
				Homing_misses++;
			}
		} else {
			obj->flags |= OF_SHOULD_BE_DEAD;
		}

		return;
	}

	// plot homing missiles on the radar
	if (((wip->wi_flags & WIF_BOMB) || (wip->wi_flags2 & WIF2_SHOWN_ON_RADAR)) && !(wip->wi_flags2 & WIF2_DONT_SHOW_ON_RADAR)) {
		if ( hud_gauge_active(HUD_RADAR) ) {
			radar_plot_object( obj );
		}
	}

	// trail missiles
	if ((wip->wi_flags & WIF_TRAIL) && !(wip->wi_flags & WIF_CORKSCREW)) {
		if ( (wp->trail_ptr != NULL ) && (wp->lssm_stage!=3))	{
			vec3d pos;
			
			if (wip->render_type == WRT_LASER) {
				// place tail origin in center of the bolt
				vm_vec_scale_add(&pos, &obj->pos, &obj->orient.vec.fvec, (wip->laser_length / 2));
			} else {
				pos = obj->pos;
			}

			if (trail_stamp_elapsed(wp->trail_ptr)) {

				trail_add_segment( wp->trail_ptr, &pos );
				
				trail_set_stamp(wp->trail_ptr);
			} else {
				trail_set_segment( wp->trail_ptr, &pos );
			}

		}
	}

	if ( wip->wi_flags & WIF_THRUSTER )	{
		ship_do_weapon_thruster_frame( wp, obj, flFrametime );	
	}

	// maybe play a "whizz sound" if close enough to view position
	#ifndef NDEBUG
	if ( Weapon_flyby_sound_enabled ) {
		weapon_maybe_play_flyby_sound(obj, wp);
	}
	#else
		weapon_maybe_play_flyby_sound(obj, wp);
	#endif	
	
	//	If our target is still valid, then update some info.
	if (wp->target_num != -1) {
		if (Objects[wp->target_num].signature == wp->target_sig) {
			float		cur_dist;
			vec3d	v0;

			vm_vec_avg(&v0, &obj->pos, &obj->last_pos);

			cur_dist = vm_vec_dist_quick(&v0, &Objects[wp->target_num].pos);

			if (cur_dist < wp->nearest_dist) {
				wp->nearest_dist = cur_dist;
			} else if (cur_dist > wp->nearest_dist + 1.0f) {
				float		dot;
				vec3d	tvec;
				ai_info	*parent_aip;

				parent_aip = NULL;
				if (obj->parent != Player_obj-Objects) {
					parent_aip = &Ai_info[Ships[Objects[obj->parent].instance].ai_index];
				}

				vm_vec_normalized_dir(&tvec, &v0, &Objects[wp->target_num].pos);
				dot = vm_vec_dot(&tvec, &Objects[wp->target_num].orient.vec.fvec);
				wp->target_num = -1;

				//	Learn!  If over-shooting or under-shooting, compensate.
				//	Really need to compensate for left/right errors.  This does no good against someone circling
				//	in a plane perpendicular to the attacker's forward vector.
				if (parent_aip != NULL) {
					if (cur_dist > 100.0f)
						parent_aip->lead_scale = 0.0f;

					if (dot < -0.1f){
						parent_aip->lead_scale += cur_dist/2000.0f;
					} else if (dot > 0.1f) {
						parent_aip->lead_scale -= cur_dist/2000.0f;
					}
					
					if (fl_abs(parent_aip->lead_scale) > 1.0f){
						parent_aip->lead_scale *= 0.9f;
					}
				}
			}
		}
	}

	if(wip->wi_flags & WIF_PARTICLE_SPEW){
		weapon_maybe_spew_particle(obj);
	}

	// a single player or multiplayer server function -- it affects actual weapon movement.
	if (wip->wi_flags & WIF_HOMING && !(wp->weapon_flags & WF_NO_HOMING)) {
		weapon_home(obj, num, frame_time);
		
		// If this is a swarm type missile,  
		if ( wp->swarm_index >= 0 ) {
			swarm_update_direction(obj, frame_time);
		}

		if( wp->cscrew_index >= 0) {
			cscrew_process_post(obj);			
		}
	} else if (wip->acceleration_time > 0.0f) {
		if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
			float t;

			t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
			obj->phys_info.speed = wp->launch_speed + MAX(0.0f, wp->weapon_max_vel - wp->launch_speed) * t;
		} else {
			obj->phys_info.speed = wip->max_speed;
			obj->phys_info.flags |= PF_CONST_VEL; // Max speed reached, can use simpler physics calculations now
		}

		vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);
	}

	//local ssm stuff
	if (wip->wi_flags2 & WIF2_LOCAL_SSM)
	{
		//go into subspace if the missile is locked and its time to warpout
		if ((wp->lssm_stage==1) && (timestamp_elapsed(wp->lssm_warpout_time)))
		{
			//if we don't have a lock at this point, just stay in normal space
			if (wp->homing_object == &obj_used_list)
			{
				wp->lssm_stage=0;
				return;
			}

			//point where to warpout
			vec3d warpout;

			//create a warp effect
			vm_vec_copy_scale(&warpout,&obj->phys_info.vel,3.0f);

			//set the time the warphole stays open, minimum of 7 seconds
			wp->lssm_warp_time = ((obj->radius * 2) / (obj->phys_info.speed)) +1.5f;
			wp->lssm_warp_time = MAX(wp->lssm_warp_time,7.0f);

			//calculate the percerentage of the warpholes life at which the missile is fully in subspace.
			wp->lssm_warp_pct = 1.0f - (3.0f/wp->lssm_warp_time);

			//create the warphole
			vm_vec_add2(&warpout,&obj->pos);
			wp->lssm_warp_idx=fireball_create(&warpout, FIREBALL_WARP, FIREBALL_WARP_EFFECT, -1,obj->radius*1.5f,1,&vmd_zero_vector,wp->lssm_warp_time,0,&obj->orient);

			if (wp->lssm_warp_idx < 0) {
				mprintf(("LSSM: Failed to create warp effect! Please report if this happens frequently.\n"));
				// Abort warping
				wp->lssm_stage = 0;
			} else {
				wp->lssm_stage = 2;
			}
		}

		//its just entered subspace subspace. don't collide or render
		if ((wp->lssm_stage==2) && (fireball_lifeleft_percent(&Objects[wp->lssm_warp_idx]) <= wp->lssm_warp_pct))
		{
			uint flags=obj->flags & ~(OF_RENDERS | OF_COLLIDES);

			obj_set_flags(obj, flags);
		
			//get the position of the target, and estimate its position when it warps out
			//so we have an idea of where it will be.
			if (wp->target_num >= 0)
			{
				vm_vec_scale_add(&wp->lssm_target_pos, &Objects[wp->target_num].pos, &Objects[wp->target_num].phys_info.vel, (float)wip->lssm_warpin_delay / 1000.0f);
			}
			else
			{
				// Our target is invalid, just jump to our position
				wp->lssm_target_pos = obj->pos;
			}

			wp->lssm_stage=3;

		}
	
		//time to warp in.
		if ((wp->lssm_stage==3) && (timestamp_elapsed(wp->lssm_warpin_time)))
		{

			vec3d warpin;
			object* target_objp=wp->homing_object;
			vec3d fvec;
			matrix orient;

			//spawn the ssm at a random point in a circle around the target
			vm_vec_random_in_circle(&warpin, &wp->lssm_target_pos, &target_objp->orient, wip->lssm_warpin_radius + target_objp->radius,1);
	
			//orient the missile properly
			vm_vec_sub(&fvec,&wp->lssm_target_pos, &warpin);
			vm_vector_2_matrix(&orient,&fvec,NULL,NULL);

			//create a warpin effect
			wp->lssm_warp_idx=fireball_create(&warpin, FIREBALL_WARP, FIREBALL_WARP_EFFECT, -1,obj->radius*1.5f,0,&vmd_zero_vector,wp->lssm_warp_time,0,&orient);
			
			if (wp->lssm_warp_idx < 0) {
				mprintf(("LSSM: Failed to create warp effect! Please report if this happens frequently.\n"));
			}

			obj->orient=orient;
			obj->pos=warpin;
			obj->phys_info.speed=0;
			obj->phys_info.desired_vel = vmd_zero_vector;
			obj->phys_info.vel = obj->phys_info.desired_vel;

			wp->lssm_stage = 4;
		}

		//done warping in.  render and collide it. let the fun begin
		// If the previous fireball creation failed just put it into normal space now
		if ((wp->lssm_stage==4) && (wp->lssm_warp_idx < 0 || fireball_lifeleft_percent(&Objects[wp->lssm_warp_idx]) <=0.5f))
		{
			vm_vec_copy_scale(&obj->phys_info.desired_vel, &obj->orient.vec.fvec, wip->lssm_stage5_vel );
			obj->phys_info.vel = obj->phys_info.desired_vel;
			obj->phys_info.speed = vm_vec_mag(&obj->phys_info.desired_vel);

			wp->lssm_stage=5;

			uint flags=obj->flags | OF_RENDERS | OF_COLLIDES;

			obj_set_flags(obj,flags);
		}
	}

	if (wip->hud_in_flight_snd >= 0 && obj->parent_sig == Player_obj->signature)
	{
		bool play_sound = false;
		switch (wip->in_flight_play_type)
		{
		case TARGETED:
			play_sound = wp->homing_object != &obj_used_list;
			break;
		case UNTARGETED:
			play_sound = wp->homing_object == &obj_used_list;
			break;
		case ALWAYS:
			play_sound = true;
			break;
		default:
			Error(LOCATION, "Unknown in-flight sound status %d!", (int) wip->in_flight_play_type);
			break;
		}

		if (play_sound)
		{
			if (wp->hud_in_flight_snd_sig < 0 || !snd_is_playing(wp->hud_in_flight_snd_sig))
			{
				wp->hud_in_flight_snd_sig = snd_play_looping(&Snds[wip->hud_in_flight_snd]);
			}
		}
	}
}

/**
 * Update weapon tracking information.
 */
void weapon_set_tracking_info(int weapon_objnum, int parent_objnum, int target_objnum, int target_is_locked, ship_subsys *target_subsys)
{
	object		*parent_objp;
	weapon		*wp;
	weapon_info	*wip;
	int targeting_same = 0;

	if ( weapon_objnum < 0 ) {
		return;
	}

	Assert(Objects[weapon_objnum].type == OBJ_WEAPON);

	wp = &Weapons[Objects[weapon_objnum].instance];
	wip = &Weapon_info[wp->weapon_info_index];

	if (wp->weapon_flags & WF_NO_HOMING) {
		return;
	}

	if (parent_objnum >= 0) {
		parent_objp = &Objects[parent_objnum];
		Assert(parent_objp->type == OBJ_SHIP);
	} else {
		parent_objp = NULL;
	}

	if (parent_objp != NULL && (Ships[parent_objp->instance].flags2 & SF2_NO_SECONDARY_LOCKON)) {
		wp->weapon_flags |= WF_NO_HOMING;
		wp->homing_object = &obj_used_list;
		wp->homing_subsys = NULL;
		wp->target_num = -1;
		wp->target_sig = -1;

		return;
	}

	if ( parent_objp == NULL || Ships[parent_objp->instance].ai_index >= 0 ) {
		int target_team = -1;
		if ( target_objnum >= 0 ) {
			int obj_type = Objects[target_objnum].type;
			if ( (obj_type == OBJ_SHIP) || (obj_type == OBJ_WEAPON) ) {
				target_team = obj_team(&Objects[target_objnum]);
			}
		}
	
		// determining if we're targeting the same team
		if (parent_objp != NULL && Ships[parent_objp->instance].team == target_team){
			targeting_same = 1;

			// Goober5000 - if we're going bonkers, pretend we're not targeting our own team
			ai_info *parent_aip = &Ai_info[Ships[parent_objp->instance].ai_index];
			if (parent_aip->active_goal != AI_GOAL_NONE && parent_aip->active_goal != AI_ACTIVE_GOAL_DYNAMIC) {
				if (parent_aip->goals[parent_aip->active_goal].flags & AIGF_TARGET_OWN_TEAM) {
					targeting_same = 0;
				}
			}
		} else {
			targeting_same = 0;
		}

		if ((target_objnum != -1) && (!targeting_same || (MULTI_DOGFIGHT && (target_team == Iff_traitor))) ) {
			wp->target_num = target_objnum;
			wp->target_sig = Objects[target_objnum].signature;
			wp->nearest_dist = 99999.0f;
			if ( (wip->wi_flags & WIF_HOMING_ASPECT) && target_is_locked) {
				wp->homing_object = &Objects[target_objnum];
				wp->homing_subsys = target_subsys;
				weapon_maybe_play_warning(wp);
			} else if ( (wip->wi_flags & WIF_HOMING_JAVELIN) && target_is_locked) {
				if ((Objects[target_objnum].type == OBJ_SHIP) &&
					( (wp->homing_subsys == NULL) ||
					  (wp->homing_subsys->system_info->type != SUBSYSTEM_ENGINE) )) {
						ship *target_ship = &Ships[Objects[target_objnum].instance];
						wp->homing_subsys = ship_get_closest_subsys_in_sight(target_ship, SUBSYSTEM_ENGINE, &Objects[weapon_objnum].pos);
						if (wp->homing_subsys == NULL) {
							wp->homing_object = &obj_used_list;
						} else {
							Assert(wp->homing_subsys->parent_objnum == target_objnum);
							wp->homing_object = &Objects[target_objnum];
							weapon_maybe_play_warning(wp);
						}
				} else {
					wp->homing_object = &Objects[target_objnum];
					wp->homing_subsys = target_subsys;
					weapon_maybe_play_warning(wp);
				}
			} else if ( wip->wi_flags & WIF_HOMING_HEAT ) {
				//	Make a heat seeking missile try to home.  If the target is outside the view cone, it will
				//	immediately drop it and try to find one in its view cone.
				if ((target_objnum != -1) && !(wip->wi_flags2 & WIF2_UNTARGETED_HEAT_SEEKER)) {
					wp->homing_object = &Objects[target_objnum];
					wp->homing_subsys = target_subsys;
					weapon_maybe_play_warning(wp);
				} else {
					wp->homing_object = &obj_used_list;
					wp->homing_subsys = NULL;
				}
			}
		} else {
			wp->target_num = -1;
			wp->target_sig = -1;
		}

		//	If missile is locked on target, increase its lifetime by 20% since missiles can be fired at limit of range
		//	as defined by velocity*lifeleft, but missiles often slow down a bit, plus can be fired at a moving away target.
		//	Confusing to many players when their missiles run out of gas before getting to target.	
		// DB - removed 7:14 pm 9/6/99. was totally messing up lifetimes for all weapons.
		//	MK, 7:11 am, 9/7/99.  Put it back in, but with a proper check here to make sure it's an aspect seeker and
		//	put a sanity check in the color changing laser code that was broken by this code.
		if (target_is_locked && (wp->target_num != -1) && (wip->wi_flags & WIF_LOCKED_HOMING) ) {
			wp->lifeleft *= LOCKED_HOMING_EXTENDED_LIFE_FACTOR;
			if (MULTIPLAYER_MASTER) {
				wp->weapon_flags |= WF_HOMING_UPDATE_NEEDED;
			}
		}

		ai_update_danger_weapon(target_objnum, weapon_objnum);		
	}
}

size_t* get_pointer_to_weapon_fire_pattern_index(int weapon_type, ship* shipp, ship_subsys * src_turret)
{
	Assert( shipp != NULL );
	ship_weapon* ship_weapon_p = &(shipp->weapons);
	if(src_turret)
	{
		ship_weapon_p = &src_turret->weapons;
	}
	Assert( ship_weapon_p != NULL );

	// search for the corresponding bank pattern index for the weapon_type that is being fired.
	// Note: Because a weapon_type may not be unique to a weapon bank per ship this search may attribute
	// the weapon to the wrong bank.  Hopefully this isn't a problem.
	for ( int pi = 0; pi < MAX_SHIP_PRIMARY_BANKS; pi++ ) {
		if ( ship_weapon_p->primary_bank_weapons[pi] == weapon_type ) {
			return &(ship_weapon_p->primary_bank_pattern_index[pi]);
		}
	}
	for ( int si = 0; si < MAX_SHIP_SECONDARY_BANKS; si++ ) {
		if ( ship_weapon_p->secondary_bank_weapons[si] == weapon_type ) {
			return &(ship_weapon_p->secondary_bank_pattern_index[si]);
		}
	}
	return NULL;
}

/**
 * Create a weapon object
 *
 * @return Index of weapon in the Objects[] array, -1 if the weapon object was not created
 */
int Weapons_created = 0;
int weapon_create( vec3d * pos, matrix * porient, int weapon_type, int parent_objnum, int group_id, int is_locked, int is_spawned, float fof_cooldown, ship_subsys * src_turret)
{
	int			n, objnum;
	int num_deleted;
	object		*objp, *parent_objp=NULL;
	weapon		*wp;
	weapon_info	*wip;

	Assert(weapon_type >= 0 && weapon_type < Num_weapon_types);

	wip = &Weapon_info[weapon_type];

	// beam weapons should never come through here!
	if(wip->wi_flags & WIF_BEAM)
	{
		Warning(LOCATION, "An attempt to fire a beam ('%s') through weapon_create() was made.\n", wip->name);
		return -1;
	}

	parent_objp = NULL;
	if(parent_objnum >= 0){
		parent_objp = &Objects[parent_objnum];
	}

	if ( (wip->num_substitution_patterns > 0) && (parent_objp != NULL)) {
		// using substitution

		// get to the instance of the gun
		Assertion( parent_objp->type == OBJ_SHIP, "Expected type OBJ_SHIP, got %d", parent_objp->type );
		Assertion( (parent_objp->instance < MAX_SHIPS) && (parent_objp->instance >= 0),
			"Ship index is %d, which is out of range [%d,%d)", parent_objp->instance, 0, MAX_SHIPS);
		ship* parent_shipp = &(Ships[parent_objp->instance]);
		Assert( parent_shipp != NULL );

		size_t *position = get_pointer_to_weapon_fire_pattern_index(weapon_type, parent_shipp, src_turret);
		Assertion( position != NULL, "'%s' is trying to fire a weapon that is not selected", Ships[parent_objp->instance].ship_name );

		++(*position);
		*position = (*position) % wip->num_substitution_patterns;

		if ( wip->weapon_substitution_pattern[*position] == -1 ) {
			// weapon doesn't want any sub
			return -1;
		} else if ( wip->weapon_substitution_pattern[*position] != weapon_type ) {
			// weapon wants to sub with weapon other than me
			return weapon_create(pos, porient, wip->weapon_substitution_pattern[*position], parent_objnum, group_id, is_locked, is_spawned, fof_cooldown);
		}
	}

	num_deleted = 0;
	if (Num_weapons >= MAX_WEAPONS-5) {

		//No, do remove for AI ships -- MK, 3/12/98  // don't need to try and delete weapons for ai ships
		//if ( !(Objects[parent_objnum].flags & OF_PLAYER_SHIP) )
		//	return -1;

		num_deleted = collide_remove_weapons();

		nprintf(("WARNING", "Deleted %d weapons because of lack of slots\n", num_deleted));
		if (num_deleted == 0){
			return -1;
		}
	}

	for (n=0; n<MAX_WEAPONS; n++ ){
		if (Weapons[n].weapon_info_index < 0){
			break;
		}
	}

	if (n == MAX_WEAPONS) {
		// if we supposedly deleted weapons above, what happened here!!!!
		if (num_deleted){
			Int3();				// get allender -- something funny is going on!!!
		}

		return -1;
	}

	// make sure we are loaded and useable
	if ( (wip->render_type == WRT_POF) && (wip->model_num < 0) ) {
		wip->model_num = model_load(wip->pofbitmap_name, 0, NULL);

		if (wip->model_num < 0) {
			Int3();
			return -1;
		}
	}

	// make sure that our textures are loaded as well
	if ( !used_weapons[weapon_type] )
		weapon_load_bitmaps(weapon_type);

	//I am hopeing that this way does not alter the input orient matrix
	//Feild of Fire code -Bobboau
	matrix morient;
	matrix *orient;

	if(porient != NULL) {
		morient = *porient;
	} else {
		morient = vmd_identity_matrix;
	}

	orient = &morient;

	float combined_fof = wip->field_of_fire;
	// If there is a fof_cooldown value, increase the spread linearly
	if (fof_cooldown != 0.0f) {
		combined_fof = wip->field_of_fire + (fof_cooldown * wip->max_fof_spread);
	}

	if(combined_fof > 0.0f){
		vec3d f;
		vm_vec_random_cone(&f, &orient->vec.fvec, combined_fof);
		vm_vec_normalize(&f);
		vm_vector_2_matrix( orient, &f, NULL, NULL);
	}

	Weapons_created++;
	objnum = obj_create( OBJ_WEAPON, parent_objnum, n, orient, pos, 2.0f, OF_RENDERS | OF_COLLIDES | OF_PHYSICS );
	Assert(objnum >= 0);
	objp = &Objects[objnum];

	// Create laser n!
	wp = &Weapons[n];

	// check if laser or dumbfire missile
	// set physics flag to allow optimization
	if ((wip->subtype == WP_LASER) || ((wip->subtype == WP_MISSILE) && !(wip->wi_flags & WIF_HOMING) && wip->acceleration_time == 0.0f)) {
		// set physics flag
		objp->phys_info.flags |= PF_CONST_VEL;
	}

	wp->start_pos = *pos;
	wp->objnum = objnum;
	wp->model_instance_num = -1;
	wp->homing_object = &obj_used_list;		//	Assume not homing on anything.
	wp->homing_subsys = NULL;
	wp->creation_time = Missiontime;
	wp->group_id = group_id;

	// we don't necessarily need a parent
	if(parent_objp != NULL){
		Assert(parent_objp->type == OBJ_SHIP);	//	Get Mike, a non-ship has fired a weapon!
		Assert((parent_objp->instance >= 0) && (parent_objp->instance < MAX_SHIPS));
		wp->team = Ships[parent_objp->instance].team;
		wp->species = Ship_info[Ships[parent_objp->instance].ship_info_index].species;
	} else {
		// ugh - we need to prevent bad array accesses
		wp->team = Iff_traitor;
		wp->species = 0;
	}
	wp->turret_subsys = NULL;
	vm_vec_zero(&wp->homing_pos);
	wp->weapon_flags = 0;
	wp->target_sig = -1;
	wp->cmeasure_ignore_list = nullptr;
	wp->det_range = wip->det_range;

	// Init the thruster info
	wp->thruster_bitmap = -1;
	wp->thruster_frame = 0.0f;
	wp->thruster_glow_bitmap = -1;
	wp->thruster_glow_noise = 1.0f;
	wp->thruster_glow_frame = 0.0f;

	// init the laser info
	wp->laser_bitmap_frame = 0.0f;
	wp->laser_glow_bitmap_frame = 0.0f;

	if ( wip->wi_flags & WIF_SWARM ) {
		wp->swarm_index = (short)swarm_create();
	} else {
		wp->swarm_index = -1;
	}		

	// if this is a particle spewing weapon, setup some stuff
	if (wip->wi_flags & WIF_PARTICLE_SPEW) {
		for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) {		// allow for multiple time values
			if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE) {
				wp->particle_spew_time[s] = -1;
				wp->particle_spew_rand = frand_range(0, PI2);	// per weapon randomness
			}
		}
	}

	// assign the network signature.  The starting sig is sent to all clients, so this call should
	// result in the same net signature numbers getting assigned to every player in the game
	if ( Game_mode & GM_MULTIPLAYER ) {
		if(wip->subtype == WP_MISSILE){
			Objects[objnum].net_signature = multi_assign_network_signature( MULTI_SIG_NON_PERMANENT );

			// for weapons that respawn, add the number of respawnable weapons to the net signature pool
			// to reserve N signatures for the spawned weapons
			if ( wip->wi_flags & WIF_SPAWN ){
                multi_set_network_signature( (ushort)(Objects[objnum].net_signature + wip->total_children_spawned), MULTI_SIG_NON_PERMANENT );
			}
		} else {
			Objects[objnum].net_signature = multi_assign_network_signature( MULTI_SIG_NON_PERMANENT );
		}
		// for multiplayer clients, when creating lasers, add some more life to the lasers.  This helps
		// to overcome some problems associated with lasers dying on client machine before they get message
		// from server saying it hit something.
	}

	//Check if we want to gen a random number
	//This is used for lifetime min/max
	float rand_val;
	if ( Game_mode & GM_NORMAL ){
		rand_val = frand();
	} else {
		rand_val = static_randf(Objects[objnum].net_signature);
	}

	wp->weapon_info_index = weapon_type;
	if(wip->life_min < 0.0f && wip->life_max < 0.0f) {
		wp->lifeleft = wip->lifetime;
	} else {
		wp->lifeleft = (rand_val) * (wip->life_max - wip->life_min) / wip->life_min;
		if((wip->wi_flags & WIF_CMEASURE) && (parent_objp != NULL) && (parent_objp->flags & OF_PLAYER_SHIP)) {
			wp->lifeleft *= The_mission.ai_profile->cmeasure_life_scale[Game_skill_level];
		}
		wp->lifeleft = wip->life_min + wp->lifeleft * (wip->life_max - wip->life_min);
	}

	if(wip->wi_flags & WIF_CMEASURE) {
		//2-frame homing check, to fend off sync errors
		Cmeasures_homing_check = 2;
	}

	//	Make remote detonate missiles look like they're getting detonated by firer simply by giving them variable lifetimes.
	if (parent_objp != NULL && !(parent_objp->flags & OF_PLAYER_SHIP) && (wip->wi_flags & WIF_REMOTE)) {
		wp->lifeleft = wp->lifeleft/2.0f + rand_val * wp->lifeleft/2.0f;
	}

	objp->phys_info.mass = wip->mass;
	objp->phys_info.side_slip_time_const = 0.0f;
	objp->phys_info.rotdamp = 0.0f;
	vm_vec_zero(&objp->phys_info.max_vel);
	objp->phys_info.max_vel.xyz.z = wip->max_speed;
	vm_vec_zero(&objp->phys_info.max_rotvel);
	objp->shield_quadrant[0] = wip->damage;
	if (wip->weapon_hitpoints > 0){
		objp->hull_strength = (float) wip->weapon_hitpoints;
	} else {
		objp->hull_strength = 0.0f;
	}

	if ( wip->render_type == WRT_POF ) {
		// this should have been checked above, but let's be extra sure
		Assert(wip->model_num >= 0);

		objp->radius = model_get_radius(wip->model_num);

		// if we intrinsic-rotate, make sure we have a model instance
		if (model_get(wip->model_num)->flags & PM_FLAG_HAS_INTRINSIC_ROTATE) {
			wp->model_instance_num = model_create_instance(false, wip->model_num);
		}
	} else if ( wip->render_type == WRT_LASER ) {
		objp->radius = wip->laser_head_radius;
	}

	//	Set desired velocity and initial velocity.
	//	For lasers, velocity is always the same.
	//	For missiles, it is a small amount plus the firing ship's velocity.
	//	For missiles, the velocity trends towards some goal.
	//	Note: If you change how speed works here, such as adding in speed of parent ship, you'll need to change the AI code
	//	that predicts collision points.  See Mike Kulas or Dave Andsager.  (Or see ai_get_weapon_speed().)
	if (wip->acceleration_time > 0.0f) {
		vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, 0.01f ); // Tiny initial velocity to avoid possible null vec issues
		objp->phys_info.vel = objp->phys_info.desired_vel;
		objp->phys_info.speed = 0.0f;
		wp->launch_speed = 0.0f;
	} else if (!(wip->wi_flags & WIF_HOMING)) {
		vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, objp->phys_info.max_vel.xyz.z );
		objp->phys_info.vel = objp->phys_info.desired_vel;
		objp->phys_info.speed = vm_vec_mag(&objp->phys_info.desired_vel);
	} else {		
		//	For weapons that home, set velocity to sum of forward component of parent's velocity and 1/4 weapon's max speed.
		//	Note that it is important to extract the forward component of the parent's velocity to factor out sliding, else
		//	the missile will not be moving forward.
		if(parent_objp != NULL){
			if (wip->free_flight_time > 0.0)
				vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, vm_vec_dot(&parent_objp->phys_info.vel, &parent_objp->orient.vec.fvec) + objp->phys_info.max_vel.xyz.z/4 );
			else
				vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, objp->phys_info.max_vel.xyz.z );
		} else {
			if (!is_locked && wip->free_flight_time > 0.0)
            {
			    vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, objp->phys_info.max_vel.xyz.z/4 );
            }
            else
            {
                vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, objp->phys_info.max_vel.xyz.z );
            }
		}
		objp->phys_info.vel = objp->phys_info.desired_vel;
		objp->phys_info.speed = vm_vec_mag(&objp->phys_info.vel);
	}

	wp->weapon_max_vel = objp->phys_info.max_vel.xyz.z;

	// Turey - maybe make the initial speed of the weapon take into account the velocity of the parent.
	// Improves aiming during gliding.
	if ((parent_objp != NULL) && (The_mission.ai_profile->flags & AIPF_USE_ADDITIVE_WEAPON_VELOCITY)) {
		float pspeed = vm_vec_mag( &parent_objp->phys_info.vel );
		vm_vec_scale_add2( &objp->phys_info.vel, &parent_objp->phys_info.vel, wip->vel_inherit_amount );
		wp->weapon_max_vel += pspeed * wip->vel_inherit_amount;
		objp->phys_info.speed = vm_vec_mag(&objp->phys_info.vel);

		if (wip->acceleration_time > 0.0f)
			wp->launch_speed += pspeed;
	}

	// create the corkscrew
	if ( wip->wi_flags & WIF_CORKSCREW ) {
		wp->cscrew_index = (short)cscrew_create(objp);
	} else {
		wp->cscrew_index = -1;
	}

	if (wip->wi_flags2 & WIF2_LOCAL_SSM)
	{

		Assert(parent_objp);		//local ssms must have a parent

		wp->lssm_warpout_time=timestamp(wip->lssm_warpout_delay);
		wp->lssm_warpin_time=timestamp(wip->lssm_warpout_delay + wip->lssm_warpin_delay);
		wp->lssm_stage=1;
	}
	else{
		wp->lssm_stage=-1;
	}


	// if this is a flak weapon shell, make it so
	// NOTE : this function will change some fundamental things about the weapon object
	if ( (wip->wi_flags & WIF_FLAK) && !(wip->wi_flags2 & WIF2_RENDER_FLAK) ) {
		obj_set_flags(&Objects[wp->objnum], Objects[wp->objnum].flags & ~(OF_RENDERS));
	}

	wp->missile_list_index = -1;
	// If this is a missile, then add it to the Missile_obj_list
	if ( wip->subtype == WP_MISSILE ) {
		wp->missile_list_index = missile_obj_list_add(objnum);
	}

	if (wip->wi_flags & WIF_TRAIL /*&& !(wip->wi_flags & WIF_CORKSCREW) */) {
		wp->trail_ptr = trail_create(&wip->tr_info);		

		if ( wp->trail_ptr != NULL )	{
			// Add two segments.  One to stay at launch pos, one to move.
			trail_add_segment( wp->trail_ptr, &objp->pos );
			trail_add_segment( wp->trail_ptr, &objp->pos );
		}
	}
	else
	{
		//If a weapon has no trails, make sure we don't try to do anything with them.
		wp->trail_ptr = NULL;
	}

	// Ensure weapon flyby sound doesn't get played for player lasers
	if ( parent_objp != NULL && parent_objp == Player_obj ) {
		wp->weapon_flags |= WF_PLAYED_FLYBY_SOUND;
	}

	wp->pick_big_attack_point_timestamp = timestamp(1);

	//	Set detail levels for POF-type weapons.
	if (Weapon_info[wp->weapon_info_index].model_num != -1) {
		polymodel * pm;
		int	i;
		pm = model_get(Weapon_info[wp->weapon_info_index].model_num);

		for (i=0; i<pm->n_detail_levels; i++){
			// for weapons, detail levels are all preset to -1
			if (wip->detail_distance[i] >= 0)
				pm->detail_depth[i] = i2fl(wip->detail_distance[i]);
			else
				pm->detail_depth[i] = (objp->radius*20.0f + 20.0f) * i;
		}

#ifndef NDEBUG
		// since debug builds always have cheats enabled, we don't necessarily get the chance
		// to enable thrusters for previously non-loaded weapons (ie, weapons_page_in_cheats())
		// when using cheat-keys, so we need to make sure and enable thrusters here if needed
		if (pm->n_thrusters > 0) {
			wip->wi_flags |= WIF_THRUSTER;
		}
#endif
	}

		// if the weapon was fired locked
	if(is_locked){
		wp->weapon_flags |= WF_LOCKED_WHEN_FIRED;
	}

	//if the weapon was spawned from a spawning type weapon
	if(is_spawned){
		wp->weapon_flags |= WF_SPAWNED;
	}

	wp->alpha_current = -1.0f;
	wp->alpha_backward = 0;

	wp->collisionOccured = false;
	wp->hud_in_flight_snd_sig = -1;

	Num_weapons++;

	// reset the damage record fields (for scoring purposes)
	wp->total_damage_received = 0.0f;
	for(int i=0;i<MAX_WEP_DAMAGE_SLOTS;i++)
	{
		wp->damage_ship[i] = 0.0f;
		wp->damage_ship_id[i] = -1;
	}

	if (Weapons_inherit_parent_collision_group) {
		Objects[objnum].collision_group_id = Objects[parent_objnum].collision_group_id;
	}

	return objnum;
}

/**
 * Spawn child weapons from object *objp.
 */
void spawn_child_weapons(object *objp)
{
	int	i, j;
	int	child_id;
	int	parent_num;
	ushort starting_sig;
	weapon	*wp = NULL;
	beam	*bp = NULL;
	weapon_info	*wip, *child_wip;
	vec3d	*opos, *fvec;

	Assertion(objp->type == OBJ_WEAPON || objp->type == OBJ_BEAM, "spawn_child_weapons() doesn't make sense for non-weapon non-beam objects; get a coder!\n");
	Assertion(objp->instance >= 0, "spawn_child_weapons() called with an object with an instance of %d; get a coder!\n", objp->instance);
	Assertion(!(objp->type == OBJ_WEAPON) || (objp->instance < MAX_WEAPONS), "spawn_child_weapons() called with a weapon with an instance of %d while MAX_WEAPONS is %d; get a coder!\n", objp->instance, MAX_WEAPONS);
	Assertion(!(objp->type == OBJ_BEAM) || (objp->instance < MAX_BEAMS), "spawn_child_weapons() called with a beam with an instance of %d while MAX_BEAMS is %d; get a coder!\n", objp->instance, MAX_BEAMS);

	if (objp->type == OBJ_WEAPON) {
		wp = &Weapons[objp->instance];
		Assertion((wp->weapon_info_index >= 0) && (wp->weapon_info_index < Num_weapon_types), "Invalid weapon_info_index of %d; get a coder!\n", wp->weapon_info_index);
		wip = &Weapon_info[wp->weapon_info_index];
	} else if (objp->type == OBJ_BEAM) {
		bp = &Beams[objp->instance];
		Assertion((bp->weapon_info_index >= 0) && (bp->weapon_info_index < Num_weapon_types), "Invalid weapon_info_index of %d; get a coder!\n", bp->weapon_info_index);
		wip = &Weapon_info[bp->weapon_info_index];
	} else {	// Let's make sure we don't do screwball things in a release build if this gets called with a non-weapon non-beam.
		return;
	}

	parent_num = objp->parent;

	if (parent_num >= 0) {
		if ((Objects[parent_num].type != objp->parent_type) || (Objects[parent_num].signature != objp->parent_sig)) {
			mprintf(("Warning: Parent of spawn weapon does not exist.  Not spawning.\n"));
			return;
		}
	}

	starting_sig = 0;

	if ( Game_mode & GM_MULTIPLAYER ) {		
		// get the next network signature and save it.  Set the next usable network signature to be
		// the passed in objects signature + 1.  We "reserved" N of these slots when we created objp
		// for it's spawned children.
		starting_sig = multi_get_next_network_signature( MULTI_SIG_NON_PERMANENT );
		multi_set_network_signature( objp->net_signature, MULTI_SIG_NON_PERMANENT );
	}

	opos = &objp->pos;
	fvec = &objp->orient.vec.fvec;

	for (i = 0; i < wip->num_spawn_weapons_defined; i++)
	{
		for (j = 0; j < wip->spawn_info[i].spawn_count; j++)
		{
			int		weapon_objnum;
			vec3d	tvec, pos;
			matrix	orient;

			child_id = wip->spawn_info[i].spawn_type;
			child_wip = &Weapon_info[child_id];

			// for multiplayer, use the static randvec functions based on the network signatures to provide
			// the randomness so that it is the same on all machines.
			if ( Game_mode & GM_MULTIPLAYER ) {
				static_rand_cone(objp->net_signature + j, &tvec, fvec, wip->spawn_info[i].spawn_angle);
			} else {
				vm_vec_random_cone(&tvec, fvec, wip->spawn_info[i].spawn_angle);
			}
			vm_vec_scale_add(&pos, opos, &tvec, objp->radius);

			// Let's allow beam-spawn! -MageKing17
			if (child_wip->wi_flags & WIF_BEAM) {
				beam_fire_info fire_info;
				memset(&fire_info, 0, sizeof(beam_fire_info));

				fire_info.accuracy = 0.000001f;		// this will guarantee a hit
				fire_info.shooter = &Objects[parent_num];
				fire_info.turret = NULL;
				fire_info.target = NULL;
				fire_info.target_subsys = NULL;
				fire_info.target_pos1 = fire_info.target_pos2 = pos;
				fire_info.bfi_flags |= BFIF_FLOATING_BEAM | BFIF_TARGETING_COORDS;
				fire_info.starting_pos = *opos;
				fire_info.beam_info_index = child_id;
				fire_info.team = static_cast<char>(obj_team(&Objects[parent_num]));

				// fire the beam
				beam_fire(&fire_info);
			} else {
				vm_vector_2_matrix(&orient, &tvec, NULL, NULL);
				weapon_objnum = weapon_create(&pos, &orient, child_id, parent_num, -1, wp->weapon_flags & WF_LOCKED_WHEN_FIRED, 1);

				//if the child inherits parent target, do it only if the parent weapon was locked to begin with
				if ((child_wip->wi_flags2 & WIF2_INHERIT_PARENT_TARGET) && (wp->homing_object != &obj_used_list))
				{
					//Deal with swarm weapons
					if (wp->swarm_index >= 0) {
						swarm_info	*swarmp;
						swarmp = &Swarm_missiles[wp->swarm_index];

						weapon_set_tracking_info(weapon_objnum, parent_num, swarmp->homing_objnum, 1, wp->homing_subsys);
					} else {
						weapon_set_tracking_info(weapon_objnum, parent_num, wp->target_num, 1, wp->homing_subsys);
					}
				}

				//	Assign a little randomness to lifeleft so they don't all disappear at the same time.
				if (weapon_objnum != -1) {
					float rand_val;

					if ( Game_mode & GM_NORMAL ){
						rand_val = frand();
					} else {
						rand_val = static_randf(objp->net_signature + j);
					}

					Weapons[Objects[weapon_objnum].instance].lifeleft *= rand_val*0.4f + 0.8f;
				}
			}
		}

	}

	// in multiplayer, reset the next network signature to the one that was saved.
	if ( Game_mode & GM_MULTIPLAYER ){
		multi_set_network_signature( starting_sig, MULTI_SIG_NON_PERMANENT );
	}
}

/**
 * Figures out whether to play disarmed or armed hit sound, checks that the
 * chosen one exists, and plays it
 */
void weapon_play_impact_sound(weapon_info *wip, vec3d *hitpos, bool is_armed)
{
	if(is_armed)
	{
		if(wip->impact_snd != -1) {
			snd_play_3d( &Snds[wip->impact_snd], hitpos, &Eye_position );
		}
	}
	else
	{
		if(wip->disarmed_impact_snd != -1) {
			snd_play_3d(&Snds[wip->disarmed_impact_snd], hitpos, &Eye_position);
		}
	}
}

/**
 * Play a sound effect when a weapon hits a ship
 *
 * To elimate the "stereo" effect of two lasers hitting at nearly
 * the same time, and to reduce the number of sound channels used,
 * only play one impact sound if IMPACT_SOUND_DELTA has elapsed
 *
 * @note Uses Weapon_impact_timer global for timer variable
 */
void weapon_hit_do_sound(object *hit_obj, weapon_info *wip, vec3d *hitpos, bool is_armed)
{
	int	is_hull_hit;
	float shield_str;

	// If non-missiles (namely lasers) expire without hitting a ship, don't play impact sound
	if	( wip->subtype != WP_MISSILE ) {		
		if ( !hit_obj ) {
			// flak weapons make sounds		
			if(wip->wi_flags & WIF_FLAK)
			{
				weapon_play_impact_sound(wip, hitpos, is_armed);				
			}
			return;
		}

		switch(hit_obj->type) {
		case OBJ_SHIP:
			// do nothing
			break;

		case OBJ_ASTEROID:
			if ( timestamp_elapsed(Weapon_impact_timer) ) {
				weapon_play_impact_sound(wip, hitpos, is_armed);	
				Weapon_impact_timer = timestamp(IMPACT_SOUND_DELTA);
			}
			return;
			break;

		default:
			return;
		}
	}

	if ( hit_obj == NULL ) {
		weapon_play_impact_sound(wip, hitpos, is_armed);
		return;
	}

	if ( timestamp_elapsed(Weapon_impact_timer) ) {

		is_hull_hit = 1;
		if ( hit_obj->type == OBJ_SHIP ) {
			shield_str = ship_quadrant_shield_strength(hit_obj, hitpos);
		} else {
			shield_str = 0.0f;
		}

		// play a shield hit if shields are above 10% max in this quadrant
		if ( shield_str > 0.1f ) {
			is_hull_hit = 0;
		}

		if ( !is_hull_hit ) {
			// Play a shield impact sound effect
			if ( hit_obj == Player_obj ) {
				snd_play_3d( &Snds[SND_SHIELD_HIT_YOU], hitpos, &Eye_position );
				// AL 12-15-97: Add missile impact sound even when shield is hit
				if ( wip->subtype == WP_MISSILE ) {
					snd_play_3d( &Snds[SND_PLAYER_HIT_MISSILE], hitpos, &Eye_position);
				}
			} else {
				snd_play_3d( &Snds[SND_SHIELD_HIT], hitpos, &Eye_position );
			}
		} else {
			// Play a hull impact sound effect
			switch ( wip->subtype ) {
				case WP_LASER:
					if ( hit_obj == Player_obj )
						snd_play_3d( &Snds[SND_PLAYER_HIT_LASER], hitpos, &Eye_position );
					else {
						weapon_play_impact_sound(wip, hitpos, is_armed);
					}
					break;
				case WP_MISSILE:
					if ( hit_obj == Player_obj ) 
						snd_play_3d( &Snds[SND_PLAYER_HIT_MISSILE], hitpos, &Eye_position);
					else {
						weapon_play_impact_sound(wip, hitpos, is_armed);
					}
					break;
				default:	
					nprintf(("Warning","WARNING ==> Cannot determine sound to play for weapon impact\n"));
					break;
			} // end switch
		}

		Weapon_impact_timer = timestamp(IMPACT_SOUND_DELTA);
	}
}

extern bool turret_weapon_has_flags(ship_weapon *swp, int flags);

/**
 * Distrupt any subsystems that fall into damage sphere of this Electronics missile
 *
 * @param ship_objp	Pointer to ship that holds subsystem
 * @param blast_pos	World pos of weapon blast
 * @param wi_index	Weapon info index of weapon causing blast
 */
void weapon_do_electronics_effect(object *ship_objp, vec3d *blast_pos, int wi_index)
{
	weapon_info			*wip;
	ship				*shipp;
	ship_subsys			*ss;
	model_subsystem		*psub;
	vec3d				subsys_world_pos;
	float				dist;

	shipp = &Ships[ship_objp->instance];
	wip = &Weapon_info[wi_index];

	for ( ss = GET_FIRST(&shipp->subsys_list); ss != END_OF_LIST(&shipp->subsys_list); ss = GET_NEXT(ss) )
	{
		psub = ss->system_info;

		// convert subsys point to world coords
		vm_vec_unrotate(&subsys_world_pos, &psub->pnt, &ship_objp->orient);
		vm_vec_add2(&subsys_world_pos, &ship_objp->pos);

		// see if subsys point is within damage sphere
		dist = vm_vec_dist_quick(blast_pos, &subsys_world_pos);
		if ( dist < wip->shockwave.outer_rad )
		{
			float disrupt_time = (float)wip->elec_time;

			//use new style electronics disruption
			if (wip->elec_use_new_style)
			{
				//if its an engine subsytem, take the multiplier into account
				if (psub->type==SUBSYSTEM_ENGINE)
				{
					disrupt_time*=wip->elec_eng_mult;
				}
	
				//if its a turret or weapon subsytem, take the multiplier into account
				if ((psub->type==SUBSYSTEM_TURRET) || (psub->type==SUBSYSTEM_WEAPONS))
				{
					//disrupt beams
					//WMC - do this even if there are other types of weapons on the turret.
					//I figure, the big fancy electronics on beams will be used for the other
					//weapons as well. No reason having two targeting computers on a turret.
					//Plus, it's easy and fast to code. :)
					if ((psub->type==SUBSYSTEM_TURRET) && turret_weapon_has_flags(&ss->weapons, WIF_BEAM))
					{
						disrupt_time*=wip->elec_beam_mult;
					}
					//disrupt other weapons
					else
					{
						disrupt_time*=wip->elec_weap_mult;
					}
				}
				
				//disrupt sensor and awacs systems.
				if ((psub->type==SUBSYSTEM_SENSORS) || (psub->flags & MSS_FLAG_AWACS))
				{
					disrupt_time*=wip->elec_sensors_mult;
				}
			}
	
			//add a little randomness to the disruption time
			disrupt_time += frand_range(-1.0f, 1.0f) * wip->elec_randomness;
		
			//disrupt this subsystem for the calculated time
			//if it turns out to be less than 0 seconds, don't bother
			if (disrupt_time > 0)
			{
				ship_subsys_set_disrupted(ss, fl2i(disrupt_time));
			}
		}
	}
}

/**
 * Calculate the damage for an object based on the location of an area-effect
 * explosion.
 *
 * @param objp			Object pointer ship receiving blast effect
 * @param pos			World pos of blast center
 * @param inner_rad		Smallest radius at which full damage is done
 * @param outer_rad		Radius at which no damage is done
 * @param max_blast		Maximum blast possible from explosion
 * @param max_damage	Maximum damage possible from explosion
 * @param blast			OUTPUT PARAMETER: receives blast value from explosion
 * @param damage		OUTPUT PARAMETER: receives damage value from explosion
 * @param limit			A limit on the area, needed for shockwave damage
 *
 * @return		No damage occurred, -1
 * @return		Damage occured, 0
 */
int weapon_area_calc_damage(object *objp, vec3d *pos, float inner_rad, float outer_rad, float max_blast, float max_damage, float *blast, float *damage, float limit)
{
	float dist;
	vec3d box_pt;

	// if object receiving the blast is a ship, use the bbox for distances
	// otherwise use the objects radius
	// could possibly exclude SIF_SMALL_SHIP (& other small objects) from using the bbox
	if (objp->type == OBJ_SHIP) {
		int inside = get_nearest_bbox_point(objp, pos, &box_pt);
		if (inside) {
			dist = 0.0001f;
		} else {
			dist = vm_vec_dist_quick(pos, &box_pt);
		}
	} else {
		dist = vm_vec_dist_quick(&objp->pos, pos) - objp->radius;
	}

	if ( (dist > outer_rad) || (dist > limit) ) {
		return -1;	// spheres don't intersect at all
	}

	if ( dist < inner_rad ) {
		// damage is maximum within inner radius
		*damage = max_damage;
		*blast = max_blast;
	} else {
		float dist_to_outer_rad_squared = (outer_rad-dist)*(outer_rad-dist);
		float total_dist_squared = (inner_rad-outer_rad)*(inner_rad-outer_rad);

		// this means the inner and outer radii are basically equal... and since we aren't within the inner radius,
		// we fudge the law of excluded middle to place ourselves outside the outer radius
		if (total_dist_squared < 0.0001f) {
			return -1;	// avoid divide-by-zero; we won't take damage anyway
		}

		// AL 2-24-98: drop off damage relative to square of distance
		Assert(dist_to_outer_rad_squared <= total_dist_squared);
		*damage = max_damage * dist_to_outer_rad_squared/total_dist_squared;
		*blast =  (dist - outer_rad) * max_blast /(inner_rad - outer_rad);
	}

	return 0;
}

/**
 * Apply the blast effects of an explosion to a ship
 *
 * @param force_apply_pos	World pos of where force is applied to object
 * @param ship_objp			Object pointer of ship receiving the blast
 * @param blast_pos			World pos of blast center
 * @param blast				Force of blast
 * @param make_shockwave	Boolean, whether to create a shockwave or not
 */
void weapon_area_apply_blast(vec3d *force_apply_pos, object *ship_objp, vec3d *blast_pos, float blast, int make_shockwave)
{
	#define	SHAKE_CONST 3000
	vec3d		force, vec_blast_to_ship, vec_ship_to_impact;
	polymodel		*pm;

	// don't waste time here if there is no blast force
	if ( blast == 0.0f )
		return;

	// apply blast force based on distance from center of explosion
	vm_vec_sub(&vec_blast_to_ship, &ship_objp->pos, blast_pos);
	vm_vec_normalize_safe(&vec_blast_to_ship);
	vm_vec_copy_scale(&force, &vec_blast_to_ship, blast );

	vm_vec_sub(&vec_ship_to_impact, blast_pos, &ship_objp->pos);

	pm = model_get(Ship_info[Ships[ship_objp->instance].ship_info_index].model_num);
	Assert ( pm != NULL );

	if (make_shockwave) {
		physics_apply_shock (&force, blast, &ship_objp->phys_info, &ship_objp->orient, &pm->mins, &pm->maxs, pm->rad);
		if (ship_objp == Player_obj) {
			joy_ff_play_vector_effect(&vec_blast_to_ship, blast * 2.0f);
		}
	} else {
		ship_apply_whack( &force, &vec_ship_to_impact, ship_objp);
	}
}

/**
 * Do the area effect for a weapon
 *
 * @param wobjp		Object pointer to weapon causing explosion
 * @param sci		Shockwave info
 * @param pos		World pos of explosion center
 * @param other_obj	Object pointer to ship that weapon impacted on (can be NULL)
 */
void weapon_do_area_effect(object *wobjp, shockwave_create_info *sci, vec3d *pos, object *other_obj)
{
	weapon_info	*wip;
	object		*objp;
	float			damage, blast;

	wip = &Weapon_info[Weapons[wobjp->instance].weapon_info_index];	

	// only blast ships and asteroids
	// And (some) weapons
	for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
		if ( (objp->type != OBJ_SHIP) && (objp->type != OBJ_ASTEROID) && (objp->type != OBJ_WEAPON) ) {
			continue;
		}
	
		if (objp->type == OBJ_WEAPON) {
			// only apply to missiles with hitpoints
			weapon_info* wip2 = &Weapon_info[Weapons[objp->instance].weapon_info_index];
			if (wip2->weapon_hitpoints <= 0)
				continue;
			if (!((wip2->wi_flags2 & WIF2_TAKES_BLAST_DAMAGE) || (wip->wi_flags2 & WIF2_CIWS)))
				continue;
		}

		if ( objp->type == OBJ_SHIP ) {
			// don't blast navbuoys
			if ( ship_get_SIF(objp->instance) & SIF_NAVBUOY ) {
				continue;
			}
		}

		if ( weapon_area_calc_damage(objp, pos, sci->inner_rad, sci->outer_rad, sci->blast, sci->damage, &blast, &damage, sci->outer_rad) == -1 ){
			continue;
		}

		// scale damage
		damage *= weapon_get_damage_scale(wip, wobjp, other_obj);		

		weapon_info* target_wip;

		switch ( objp->type ) {
		case OBJ_SHIP:
			// If we're doing an AoE Electronics blast, do the electronics stuff (unless it also has the regular "electronics"
			// flag and this is the ship the missile directly impacted; then leave it for the regular code below) -MageKing17
			if ( (wip->wi_flags3 & WIF3_AOE_ELECTRONICS) && !((objp->flags & OF_INVULNERABLE) || ((objp == other_obj) && (wip->wi_flags & WIF_ELECTRONICS))) ) {
				weapon_do_electronics_effect(objp, pos, Weapons[wobjp->instance].weapon_info_index);
			}
			ship_apply_global_damage(objp, wobjp, pos, damage);
			weapon_area_apply_blast(NULL, objp, pos, blast, 0);
			break;
		case OBJ_ASTEROID:
			asteroid_hit(objp, NULL, NULL, damage);
			break;
		case OBJ_WEAPON:
			target_wip = &Weapon_info[Weapons[objp->instance].weapon_info_index];
			if (target_wip->armor_type_idx >= 0)
				damage = Armor_types[target_wip->armor_type_idx].GetDamage(damage, wip->damage_type_idx, 1.0f);

			objp->hull_strength -= damage;
			if (objp->hull_strength < 0.0f) {
				Weapons[objp->instance].lifeleft = 0.01f;
				Weapons[objp->instance].weapon_flags |= WF_DESTROYED_BY_WEAPON;
			}
			break;
		default:
			Int3();
			break;
		} 	

	}	// end for

	// if this weapon has the "Electronics" flag set, then disrupt subsystems in sphere
	if ( (other_obj != NULL) && (wip->wi_flags & WIF_ELECTRONICS) ) {
		if ( other_obj->type == OBJ_SHIP ) {
			weapon_do_electronics_effect(other_obj, pos, Weapons[wobjp->instance].weapon_info_index);
		}
	}
}

//	----------------------------------------------------------------------
//	weapon_armed(weapon)
//
//	Call to figure out if a weapon is armed or not
//
//Weapon is armed when...
//1: Weapon is shot down by weapon
//OR
//1: weapon is destroyed before arm time
//2: weapon is destroyed before arm distance from ship
//3: weapon is outside arm radius from target ship
bool weapon_armed(weapon *wp, bool hit_target)
{
	Assert(wp != NULL);

	weapon_info *wip = &Weapon_info[wp->weapon_info_index];

	if((wp->weapon_flags & WF_DESTROYED_BY_WEAPON)
		&& !wip->arm_time
		&& wip->arm_dist == 0.0f
		&& wip->arm_radius == 0.0f)
	{
		return false;
	}
	else
	{
		object *wobj = &Objects[wp->objnum];
		object *pobj;

		if(wobj->parent > -1) {
			pobj = &Objects[wobj->parent];
		} else {
			pobj = NULL;
		}

		if(		((wip->arm_time) && ((Missiontime - wp->creation_time) < wip->arm_time))
			|| ((wip->arm_dist) && (pobj != NULL && pobj->type != OBJ_NONE && (vm_vec_dist(&wobj->pos, &pobj->pos) < wip->arm_dist))))
		{
			return false;
		}
		if(wip->arm_radius && (!hit_target)) {
			if(wp->homing_object == &obj_used_list)
				return false;
			if(IS_VEC_NULL(&wp->homing_pos) || vm_vec_dist(&wobj->pos, &wp->homing_pos) > wip->arm_radius)
				return false;
		}
	}

	return true;
}


/**
 * Called when a weapon hits something (or, in the case of
 * missiles explodes for any particular reason)
 */
void weapon_hit( object * weapon_obj, object * other_obj, vec3d * hitpos, int quadrant )
{
	Assert(weapon_obj != NULL);
	if(weapon_obj == NULL){
		return;
	}
	Assert((weapon_obj->type == OBJ_WEAPON) && (weapon_obj->instance >= 0) && (weapon_obj->instance < MAX_WEAPONS));
	if((weapon_obj->type != OBJ_WEAPON) || (weapon_obj->instance < 0) || (weapon_obj->instance >= MAX_WEAPONS)){
		return;
	}

	int			num = weapon_obj->instance;
	int			weapon_type = Weapons[num].weapon_info_index;
	int			expl_ani_handle;
	weapon_info	*wip;
	weapon *wp;
	bool		hit_target = false;

	object      *other_objp;
	ship_obj	*so;
	ship		*shipp;
	int         objnum;

	Assert((weapon_type >= 0) && (weapon_type < MAX_WEAPON_TYPES));
	if((weapon_type < 0) || (weapon_type >= MAX_WEAPON_TYPES)){
		return;
	}
	wp = &Weapons[weapon_obj->instance];
	wip = &Weapon_info[weapon_type];
	objnum = wp->objnum;

	// check if the weapon actually hit the intended target
	if (wp->homing_object != &obj_used_list)
		if (wp->homing_object == other_obj)
			hit_target = true;

	//This is an expensive check
	bool armed_weapon = weapon_armed(&Weapons[num], hit_target);

	// if this is the player ship, and is a laser hit, skip it. wait for player "pain" to take care of it
	if ((other_obj != Player_obj) || (wip->subtype != WP_LASER) || !MULTIPLAYER_CLIENT) {
		weapon_hit_do_sound(other_obj, wip, hitpos, armed_weapon);
	}

	if ( wip->impact_weapon_expl_index > -1 && armed_weapon)
	{
		expl_ani_handle = Weapon_explosions.GetAnim(wip->impact_weapon_expl_index, hitpos, wip->impact_explosion_radius);
		particle_create( hitpos, &vmd_zero_vector, 0.0f, wip->impact_explosion_radius, PARTICLE_BITMAP_PERSISTENT, expl_ani_handle );
	}
	else if(wip->dinky_impact_weapon_expl_index > -1 && !armed_weapon)
	{
		expl_ani_handle = Weapon_explosions.GetAnim(wip->dinky_impact_weapon_expl_index, hitpos, wip->dinky_impact_explosion_radius);
		particle_create( hitpos, &vmd_zero_vector, 0.0f, wip->dinky_impact_explosion_radius, PARTICLE_BITMAP_PERSISTENT, expl_ani_handle );
	}

	if((other_obj != NULL) && (quadrant == -1) && (wip->piercing_impact_weapon_expl_index > -1 && armed_weapon)) {
		if ((other_obj->type == OBJ_SHIP) || (other_obj->type == OBJ_DEBRIS)) {

			int ok_to_draw = 1;

			if (other_obj->type == OBJ_SHIP) {
				float draw_limit, hull_pct;
				int dmg_type_idx, piercing_type;

				shipp = &Ships[other_obj->instance];

				hull_pct = other_obj->hull_strength / shipp->ship_max_hull_strength;
				dmg_type_idx = wip->damage_type_idx;
				draw_limit = Ship_info[shipp->ship_info_index].piercing_damage_draw_limit;
				
				if (shipp->armor_type_idx != -1) {
					piercing_type = Armor_types[shipp->armor_type_idx].GetPiercingType(dmg_type_idx);
					if (piercing_type == SADTF_PIERCING_DEFAULT) {
						draw_limit = Armor_types[shipp->armor_type_idx].GetPiercingLimit(dmg_type_idx);
					} else if ((piercing_type == SADTF_PIERCING_NONE) || (piercing_type == SADTF_PIERCING_RETAIL)) {
						ok_to_draw = 0;
					}
				}

				if (hull_pct > draw_limit)
					ok_to_draw = 0;
			}

			if (ok_to_draw) {
				particle_emitter pe;
				vec3d null_v;

				vm_vec_zero(&null_v);
				expl_ani_handle = Weapon_explosions.GetAnim(wip->piercing_impact_weapon_expl_index, hitpos, wip->piercing_impact_explosion_radius);

				pe.max_vel = 2.0f * wip->piercing_impact_particle_velocity;
				pe.min_vel = 0.5f * wip->piercing_impact_particle_velocity;
				pe.max_life = 2.0f * wip->piercing_impact_particle_life;
				pe.min_life = 0.25f * wip->piercing_impact_particle_life;
				pe.num_high = 2 * wip->piercing_impact_particle_count;
				pe.num_low =  wip->piercing_impact_particle_count / 2;
				pe.pos = weapon_obj->pos;
				pe.normal = weapon_obj->last_orient.vec.fvec;
				pe.normal_variance = wip->piercing_impact_particle_variance;
				pe.max_rad = 2.0f * wip->piercing_impact_explosion_radius;
				pe.min_rad = 0.5f * wip->piercing_impact_explosion_radius;
				pe.vel = null_v;

				particle_emit(&pe, PARTICLE_BITMAP, expl_ani_handle, 10.0f);

				if (wip->piercing_impact_particle_back_velocity != 0.0f) {

					pe.max_vel = 2.0f * wip->piercing_impact_particle_back_velocity;
					pe.min_vel = 0.5f * wip->piercing_impact_particle_back_velocity;
					pe.num_high /= 2;
					pe.num_low /= 2;

					particle_emit(&pe, PARTICLE_BITMAP, expl_ani_handle, 10.0f);
				}
			}
		}
	}

	// For all objects that had this weapon as a target, wipe it out, forcing find of a new enemy
	for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
		other_objp = &Objects[so->objnum];
		Assert(other_objp->instance != -1);
        
		shipp = &Ships[other_objp->instance];
		Assert(shipp->ai_index != -1);
        
		ai_info	*aip = &Ai_info[shipp->ai_index];
        
		if (aip->target_objnum == objnum) {
			set_target_objnum(aip, -1);
			//	If this ship had a dynamic goal of chasing this weapon, clear the dynamic goal.
			if (aip->resume_goal_time != -1)
				aip->active_goal = AI_GOAL_NONE;
		}
        
		if (aip->goal_objnum == objnum) {
			aip->goal_objnum = -1;
			aip->goal_signature = -1;
		}
        
		if (aip->guard_objnum == objnum) {
			aip->guard_objnum = -1;
			aip->guard_signature = -1;
		}
        
		if (aip->hitter_objnum == objnum) {
			aip->hitter_objnum = -1;
        }
	}

	// single player and multiplayer masters evaluate the scoring and kill stuff
	if (!MULTIPLAYER_CLIENT) {
		//If this is a bomb, set it up for scoring. -Halleck
		if (wip->wi_flags & WIF_BOMB) {
			scoring_eval_kill_on_weapon(weapon_obj, other_obj);
		}
	}

	weapon_obj->flags |= OF_SHOULD_BE_DEAD;

	//Set shockwaves flag
	int sw_flag = SW_WEAPON;

	if ( ((other_obj) && (other_obj->type == OBJ_WEAPON)) || (Weapons[num].weapon_flags & WF_DESTROYED_BY_WEAPON)) {
		sw_flag |= SW_WEAPON_KILL;
	}

	//Which shockwave?
	shockwave_create_info *sci = &wip->shockwave;
	if(!armed_weapon) {
		sci = &wip->dinky_shockwave;
	}

	// check if this is an area effect weapon (i.e. has a blast radius)
	if (sci->inner_rad != 0.0f || sci->outer_rad != 0.0f)
	{
		if(sci->speed > 0.0f) {
			shockwave_create(OBJ_INDEX(weapon_obj), hitpos, sci, sw_flag, -1);
		}
		else {
			weapon_do_area_effect(weapon_obj, sci, hitpos, other_obj);
		}
	}

	// check if this is an EMP weapon
	if(wip->wi_flags & WIF_EMP){
		emp_apply(&weapon_obj->pos, wip->shockwave.inner_rad, wip->shockwave.outer_rad, wip->emp_intensity, wip->emp_time, (wip->wi_flags3 & WIF3_USE_EMP_TIME_FOR_CAPSHIP_TURRETS) != 0);
	}	

	// spawn weapons - note the change from FS 1 multiplayer.
	if (wip->wi_flags & WIF_SPAWN){
		if (!((wip->wi_flags3 & WIF3_DONT_SPAWN_IF_SHOT) && (sw_flag == SW_WEAPON_KILL))){			// prevent spawning of children if shot down and the dont spawn if shot flag is set (DahBlount)
			spawn_child_weapons(weapon_obj);
		}
	}	
}

void weapon_detonate(object *objp)
{
	Assert(objp != NULL);
	if(objp == NULL){
		return;
	}
	Assert((objp->type == OBJ_WEAPON) && (objp->instance >= 0));
	if((objp->type != OBJ_WEAPON) || (objp->instance < 0)){
		return;
	}	

	// send a detonate packet in multiplayer
	if(MULTIPLAYER_MASTER){
		send_weapon_detonate_packet(objp);
	}

	// call weapon hit
	// Wanderer - use last frame pos for the corkscrew missiles
	if ( (Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags & WIF_CORKSCREW) ) {
		weapon_hit(objp, NULL, &objp->last_pos);
	} else {
		weapon_hit(objp, NULL, &objp->pos);
	}
}


// Group_id:  If you should quad lasers, they should all have the same group id.  
// This will be used to optimize lighting, since each group only needs to cast one light.
// Call this to get a new group id, then pass it to each weapon_create call for all the
// weapons in the group.   Number will be between 0 and WEAPON_MAX_GROUP_IDS and will
// get reused.
int weapon_create_group_id()
{
	static int current_id = 0;

	int n = current_id;
	
	current_id++;
	if ( current_id >= WEAPON_MAX_GROUP_IDS )	{
		current_id = 0;
	}
	return n;
}

/**
 * Call before weapons_page_in to mark a weapon as used
 */
void weapon_mark_as_used(int weapon_type)
{
	if (weapon_type < 0)
		return;

	if ( used_weapons == NULL )
		return;

	Assert( weapon_type < MAX_WEAPON_TYPES );

	if (weapon_type < Num_weapon_types) {
		used_weapons[weapon_type]++;
	}
}

void weapons_page_in()
{
	int i, j, idx;

	Assert( used_weapons != NULL );

	// for weapons in weaponry pool
	for (i = 0; i < Num_teams; i++) {
		for (j = 0; j < Team_data[i].num_weapon_choices; j++) {
			used_weapons[Team_data[i].weaponry_pool[j]] += Team_data[i].weaponry_count[j];
		}
	}

	// this grabs all spawn weapon types (Cluster Baby, etc.) which can't be
	// assigned directly to a ship
	for (i = 0; i < Num_weapon_types; i++) {
		// we only want entries that already exist
		if ( !used_weapons[i] )
			continue;

		// if it's got a spawn type then grab it
        for (j = 0; j < Weapon_info[i].num_spawn_weapons_defined; j++)
        {
            used_weapons[(int)Weapon_info[i].spawn_info[j].spawn_type]++;
        }
	}

	// release anything loaded that we don't have marked as used for this mission
	if ( !Cmdline_load_all_weapons )
		weapon_release_bitmaps();

	// Page in bitmaps for all used weapons
	for (i = 0; i < Num_weapon_types; i++) {
		if ( !Cmdline_load_all_weapons ) {
			if ( !used_weapons[i] ) {
				nprintf(("Weapons", "Not loading weapon id %d (%s)\n", i, Weapon_info[i].name));
				continue;
			}
		}

		weapon_load_bitmaps(i);

		weapon_info *wip = &Weapon_info[i];

		wip->wi_flags &= (~WIF_THRUSTER);		// Assume no thrusters
		
		switch (wip->render_type)
		{
			case WRT_POF:
			{
				wip->model_num = model_load( wip->pofbitmap_name, 0, NULL );

				polymodel *pm = model_get( wip->model_num );

				// If it has a model, and the model pof has thrusters, then set
				// the flags
				if (pm->n_thrusters > 0) {
					wip->wi_flags |= WIF_THRUSTER;
				}
		
				for (j = 0; j < pm->n_textures; j++)
					pm->maps[j].PageIn();

				break;
			}

			case WRT_LASER:
			{
				bm_page_in_texture( wip->laser_bitmap.first_frame );
				bm_page_in_texture( wip->laser_glow_bitmap.first_frame );

				break;
			}

			default:
				Assertion(wip->render_type != WRT_POF && wip->render_type != WRT_LASER, "Weapon %s does not have a valid rendering type. Type passed: %d\n", wip->name, wip->render_type);	// Invalid weapon rendering type.
		}

		wip->external_model_num = -1;

		if ( strlen(wip->external_model_name) )
			wip->external_model_num = model_load( wip->external_model_name, 0, NULL );

		if (wip->external_model_num == -1)
			wip->external_model_num = wip->model_num;


		//Load shockwaves
		shockwave_create_info_load(&wip->shockwave);
		shockwave_create_info_load(&wip->dinky_shockwave);

		//Explosions
		Weapon_explosions.PageIn(wip->impact_weapon_expl_index);
		Weapon_explosions.PageIn(wip->dinky_impact_weapon_expl_index);
		Weapon_explosions.PageIn(wip->flash_impact_weapon_expl_index);
		Weapon_explosions.PageIn(wip->piercing_impact_weapon_expl_index);

		// trail bitmaps
		if ( (wip->wi_flags & WIF_TRAIL) && (wip->tr_info.texture.bitmap_id > -1) )
			bm_page_in_texture( wip->tr_info.texture.bitmap_id );

		// if this is a beam weapon, page in its stuff
		if (wip->wi_flags & WIF_BEAM) {
			// all beam sections
			for (idx = 0; idx < wip->b_info.beam_num_sections; idx++)
				bm_page_in_texture(wip->b_info.sections[idx].texture.first_frame);

			// muzzle glow
			bm_page_in_texture(wip->b_info.beam_glow.first_frame);

			// particle ani
			bm_page_in_texture(wip->b_info.beam_particle_ani.first_frame);
		}

		if (wip->wi_flags & WIF_PARTICLE_SPEW) {
			for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) {	// looped, multi particle spew -nuke
				if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE) {
					bm_page_in_texture(wip->particle_spewers[s].particle_spew_anim.first_frame);
				}
			}
		}

		// muzzle flashes
		if (wip->muzzle_flash >= 0)
			mflash_mark_as_used(wip->muzzle_flash);

		bm_page_in_texture(wip->thruster_flame.first_frame);
		bm_page_in_texture(wip->thruster_glow.first_frame);
	}
}

/**
 * Page_in function for cheaters, grabs all weapons that weren't already in a mission
 * and loads the models for them.
 *
 * Non-model graphics elements will get loaded when they are rendered for the first time.  
 * Maybe not the best way to do this but faster and a lot less error prone.
 */
void weapons_page_in_cheats()
{
	int i;

	// don't bother if they are all loaded already
	if ( Cmdline_load_all_weapons )
		return;

	Assert( used_weapons != NULL );

	// force a page in of all muzzle flashes
	mflash_page_in(true);

	// page in models for all weapon types that aren't already loaded
	for (i = 0; i < Num_weapon_types; i++) {
		// skip over anything that's already loaded
		if (used_weapons[i])
			continue;

		weapon_load_bitmaps(i);

		weapon_info *wip = &Weapon_info[i];
		
		wip->wi_flags &= (~WIF_THRUSTER);		// Assume no thrusters

		if ( wip->render_type == WRT_POF ) {
			wip->model_num = model_load( wip->pofbitmap_name, 0, NULL );
				
			polymodel *pm = model_get( wip->model_num );
				
			// If it has a model, and the model pof has thrusters, then set
			// the flags
			if ( pm->n_thrusters > 0 )	{
				wip->wi_flags |= WIF_THRUSTER;
			}
		}
		
		wip->external_model_num = -1;
		
		if ( strlen(wip->external_model_name) )
			wip->external_model_num = model_load( wip->external_model_name, 0, NULL );

		if (wip->external_model_num == -1)
			wip->external_model_num = wip->model_num;
		
		
		//Load shockwaves
		shockwave_create_info_load(&wip->shockwave);
		shockwave_create_info_load(&wip->dinky_shockwave);

		used_weapons[i]++;
	}
}

/**
 * Get the "color" of the laser at the given moment (since glowing lasers can cycle colors)
 */
void weapon_get_laser_color(color *c, object *objp)
{
	weapon *wep;
	weapon_info *winfo;
	float pct;

	// sanity
	if (c == NULL)
		return;

	// sanity
	Assert(objp != NULL);
	Assert(objp->type == OBJ_WEAPON);
	Assert(objp->instance >= 0);
	Assert(Weapons[objp->instance].weapon_info_index >= 0);

	if ( (objp == NULL) || (objp->type != OBJ_WEAPON) || (objp->instance < 0) || (Weapons[objp->instance].weapon_info_index < 0) )
		return;

	wep = &Weapons[objp->instance];
	winfo = &Weapon_info[wep->weapon_info_index];

	// if we're a one-color laser
	if ( (winfo->laser_color_2.red == winfo->laser_color_1.red) && (winfo->laser_color_2.green == winfo->laser_color_1.green) && (winfo->laser_color_2.blue == winfo->laser_color_1.blue) ) {
		*c = winfo->laser_color_1;
		return;
	}

	int r = winfo->laser_color_1.red;
	int g = winfo->laser_color_1.green;
	int b = winfo->laser_color_1.blue;

	// lifetime pct
	pct = 1.0f - (wep->lifeleft / winfo->lifetime);
	CLAMP(pct, 0.0f, 0.5f);

	if (pct > 0.0f) {
		pct *= 2.0f;

		r += fl2i((winfo->laser_color_2.red - winfo->laser_color_1.red) * pct);
		g += fl2i((winfo->laser_color_2.green - winfo->laser_color_1.green) * pct);
		b += fl2i((winfo->laser_color_2.blue - winfo->laser_color_1.blue) * pct);
	}

	// otherwise interpolate between the colors
	gr_init_color( c, r, g, b );
}

// default weapon particle spew data

int Weapon_particle_spew_count = 1;
int Weapon_particle_spew_time = 25;
float Weapon_particle_spew_vel = 0.4f;
float Weapon_particle_spew_radius = 2.0f;
float Weapon_particle_spew_lifetime = 0.15f;
float Weapon_particle_spew_scale = 0.8f;

/**
 * For weapons flagged as particle spewers, spew particles. wheee
 */
void weapon_maybe_spew_particle(object *obj)
{
	weapon *wp;
	weapon_info *wip;
	int idx;

	// check some stuff
	Assert(obj->type == OBJ_WEAPON);
	Assert(obj->instance >= 0);
	Assert(Weapons[obj->instance].weapon_info_index >= 0);
	Assert(Weapon_info[Weapons[obj->instance].weapon_info_index].wi_flags & WIF_PARTICLE_SPEW);
	
	wp = &Weapons[obj->instance];
	wip = &Weapon_info[wp->weapon_info_index];
	vec3d spawn_pos, spawn_vel, output_pos, output_vel, input_pos, input_vel;

	for (int psi = 0; psi < MAX_PARTICLE_SPEWERS; psi++) {	// iterate through spewers	-nuke
		if (wip->particle_spewers[psi].particle_spew_type != PSPEW_NONE) {
			// if the weapon's particle timestamp has elapsed
			if ((wp->particle_spew_time[psi] == -1) || timestamp_elapsed(wp->particle_spew_time[psi])) {
				// reset the timestamp
				wp->particle_spew_time[psi] = timestamp(wip->particle_spewers[0].particle_spew_time);

				// turn normals and origins to world space if we need to
				if (!vm_vec_same(&wip->particle_spewers[psi].particle_spew_offset, &vmd_zero_vector)) {	// don't xform unused vectors
					vm_vec_unrotate(&spawn_pos, &wip->particle_spewers[psi].particle_spew_offset, &obj->orient);
				} else {
					spawn_pos = vmd_zero_vector;
				}

				if (!vm_vec_same(&wip->particle_spewers[psi].particle_spew_velocity, &vmd_zero_vector)) {
					vm_vec_unrotate(&spawn_vel, &wip->particle_spewers[psi].particle_spew_velocity, &obj->orient);
				} else {
					spawn_vel = vmd_zero_vector;
				}

				// spew some particles
				if (wip->particle_spewers[psi].particle_spew_type == PSPEW_DEFAULT)	// default pspew type
				{		// do the default pspew
						vec3d direct, direct_temp, particle_pos;
						vec3d null_vec = ZERO_VECTOR;
						vec3d vel;
						float ang;

					for (idx = 0; idx < wip->particle_spewers[psi].particle_spew_count; idx++) {
						// get the backward vector of the weapon
						direct = obj->orient.vec.fvec;
						vm_vec_negate(&direct);

						// randomly perturb x, y and z
						
						// uvec
						ang = frand_range(-PI_2,PI_2);	// fl_radian(frand_range(-90.0f, 90.0f));	-optimized by nuke
						vm_rot_point_around_line(&direct_temp, &direct, ang, &null_vec, &obj->orient.vec.fvec);			
						direct = direct_temp;
						vm_vec_scale(&direct, wip->particle_spewers[psi].particle_spew_scale);

						// rvec
						ang = frand_range(-PI_2,PI_2);	// fl_radian(frand_range(-90.0f, 90.0f));	-optimized by nuke
						vm_rot_point_around_line(&direct_temp, &direct, ang, &null_vec, &obj->orient.vec.rvec);			
						direct = direct_temp;
						vm_vec_scale(&direct, wip->particle_spewers[psi].particle_spew_scale);

						// fvec
						ang = frand_range(-PI_2,PI_2);	// fl_radian(frand_range(-90.0f, 90.0f));	-optimized by nuke
						vm_rot_point_around_line(&direct_temp, &direct, ang, &null_vec, &obj->orient.vec.uvec);			
						direct = direct_temp;
						vm_vec_scale(&direct, wip->particle_spewers[psi].particle_spew_scale);

						// get a velocity vector of some percentage of the weapon's velocity
						vel = obj->phys_info.vel;
						vm_vec_scale(&vel, wip->particle_spewers[psi].particle_spew_vel);

						// maybe add in offset and initial velocity
						if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add in particle velocity if its available
							vm_vec_add2(&vel, &spawn_vel);
						}
						if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if available
							vm_vec_add2(&direct, &spawn_pos);
						}

						if (wip->wi_flags & WIF_CORKSCREW) {
							vm_vec_add(&particle_pos, &obj->last_pos, &direct);
						} else {
							vm_vec_add(&particle_pos, &obj->pos, &direct);
						}

						// emit the particle
						if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
							particle_create(&particle_pos, &vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_SMOKE);
						} else {
							particle_create(&particle_pos, &vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_BITMAP, wip->particle_spewers[psi].particle_spew_anim.first_frame);
						}
					}
				} else if (wip->particle_spewers[psi].particle_spew_type == PSPEW_HELIX) { // helix
					float segment_length = wip->max_speed * flFrametime; // determine how long the segment is
					float segment_angular_length = PI2 * wip->particle_spewers[psi].particle_spew_rotation_rate * flFrametime; 	// determine how much the segment rotates
					float rotation_value = (wp->lifeleft * PI2 * wip->particle_spewers[psi].particle_spew_rotation_rate) + wp->particle_spew_rand; // calculate a rotational start point based on remaining life
					float inc = 1.0f / wip->particle_spewers[psi].particle_spew_count;	// determine our incriment
					float particle_rot;
					vec3d input_pos_l = ZERO_VECTOR;
					
					for (float is = 0; is < 1; is += inc ) { // use iterator as a scaler
						particle_rot = rotation_value + (segment_angular_length * is); // find what point of the rotation were at
						input_vel.xyz.x = sinf(particle_rot) * wip->particle_spewers[psi].particle_spew_scale; // determine x/y velocity based on scale and rotation
						input_vel.xyz.y = cosf(particle_rot) * wip->particle_spewers[psi].particle_spew_scale;
						input_vel.xyz.z = wip->max_speed * wip->particle_spewers[psi].particle_spew_vel; // velocity inheritance
						vm_vec_unrotate(&output_vel, &input_vel, &obj->orient);				// orient velocity to weapon
						input_pos_l.xyz.x = input_vel.xyz.x * flFrametime * (1.0f - is);	// interpolate particle motion
						input_pos_l.xyz.y = input_vel.xyz.y * flFrametime * (1.0f - is);
						input_pos_l.xyz.z = segment_length * is;							// position particle correctly on the z axis
						vm_vec_unrotate(&input_pos, &input_pos_l, &obj->orient);			// orient to weapon
						vm_vec_sub(&output_pos, &obj->pos, &input_pos);						// translate to world space

						//maybe add in offset and initial velocity
						if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add particle velocity if needed
							vm_vec_add2(&output_vel, &spawn_vel);
						}
						if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if needed
							vm_vec_add2(&output_pos, &spawn_pos);
						}

						//emit particles
						if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
							particle_create(&output_pos, &output_vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_SMOKE);
						} else {
							particle_create(&output_pos, &output_vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_BITMAP, wip->particle_spewers[psi].particle_spew_anim.first_frame);
						}
					}
				} else if (wip->particle_spewers[psi].particle_spew_type == PSPEW_SPARKLER) { // sparkler
					vec3d temp_vel;
					output_vel = obj->phys_info.vel;
					vm_vec_scale(&output_vel, wip->particle_spewers[psi].particle_spew_vel);

					for (idx = 0; idx < wip->particle_spewers[psi].particle_spew_count; idx++) {
						// create a random unit vector and scale it
						vm_vec_rand_vec_quick(&input_vel);
						vm_vec_scale(&input_vel, wip->particle_spewers[psi].particle_spew_scale);
						
						if (wip->particle_spewers[psi].particle_spew_z_scale != 1.0f) {	// don't do the extra math for spherical effect
							temp_vel = input_vel;
							temp_vel.xyz.z *= wip->particle_spewers[psi].particle_spew_z_scale;	// for an ovoid particle effect to better combine with laser effects
							vm_vec_unrotate(&input_vel, &temp_vel, &obj->orient);				// so it has to be rotated
						}

						vm_vec_add2(&output_vel, &input_vel); // add to weapon velocity
						output_pos = obj->pos;

						// maybe add in offset and initial velocity
						if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add particle velocity if needed
							vm_vec_add2(&output_vel, &spawn_vel);
						}
						if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if needed
							vm_vec_add2(&output_pos, &spawn_pos);
						}

						// emit particles
						if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
							particle_create(&output_pos, &output_vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_SMOKE);
						} else {
							particle_create(&output_pos, &output_vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_BITMAP, wip->particle_spewers[psi].particle_spew_anim.first_frame);
						}
					}
				} else if (wip->particle_spewers[psi].particle_spew_type == PSPEW_RING) {
					float inc = PI2 / wip->particle_spewers[psi].particle_spew_count;	

					for (float ir = 0; ir < PI2; ir += inc) { // use iterator for rotation
						input_vel.xyz.x = sinf(ir) * wip->particle_spewers[psi].particle_spew_scale; // generate velocity from rotation data
						input_vel.xyz.y = cosf(ir) * wip->particle_spewers[psi].particle_spew_scale;
						input_vel.xyz.z = obj->phys_info.fspeed * wip->particle_spewers[psi].particle_spew_vel;
						vm_vec_unrotate(&output_vel, &input_vel, &obj->orient); // rotate it to model

						output_pos = obj->pos;

						// maybe add in offset amd iitial velocity
						if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add particle velocity if needed
							vm_vec_add2(&output_vel, &spawn_vel);
						}
						if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if needed
							vm_vec_add2(&output_pos, &spawn_pos);
						}

						// emit particles
						if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
							particle_create(&output_pos, &output_vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_SMOKE);
						} else {
							particle_create(&output_pos, &output_vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_BITMAP, wip->particle_spewers[psi].particle_spew_anim.first_frame);
						}
					}
				} else if (wip->particle_spewers[psi].particle_spew_type == PSPEW_PLUME) {
					float ang_rand, len_rand, sin_ang, cos_ang;
					vec3d input_pos_l = ZERO_VECTOR;
					
					for (int i = 0; i < wip->particle_spewers[psi].particle_spew_count; i++) {
						// use polar coordinates to ensure a disk shaped spew plane
						ang_rand = frand_range(-PI,PI);
						len_rand = frand() * wip->particle_spewers[psi].particle_spew_scale;
						sin_ang = sinf(ang_rand);
						cos_ang = cosf(ang_rand);
						// compute velocity
						input_vel.xyz.x = wip->particle_spewers[psi].particle_spew_z_scale * -sin_ang;
						input_vel.xyz.y = wip->particle_spewers[psi].particle_spew_z_scale * -cos_ang;
						input_vel.xyz.z = obj->phys_info.fspeed * wip->particle_spewers[psi].particle_spew_vel;
						vm_vec_unrotate(&output_vel, &input_vel, &obj->orient); // rotate it to model
						// place particle on a disk prependicular to the weapon normal and rotate to model space
						input_pos_l.xyz.x = sin_ang * len_rand;
						input_pos_l.xyz.y = cos_ang * len_rand;
						vm_vec_unrotate(&input_pos, &input_pos_l, &obj->orient); // rotate to world
						vm_vec_sub(&output_pos, &obj->pos, &input_pos); // translate to world
						
						// maybe add in offset amd iitial velocity
						if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add particle velocity if needed
							vm_vec_add2(&output_vel, &spawn_vel);
						}
						if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if needed
							vm_vec_add2(&output_pos, &spawn_pos);
						}

						//emit particles
						if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
							particle_create(&output_pos, &output_vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_SMOKE);
						} else {
							particle_create(&output_pos, &output_vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_BITMAP, wip->particle_spewers[psi].particle_spew_anim.first_frame);
						}
					}
				}
			}
		}
	}
}

/**
 * Debug console functionality
 */
void dcf_pspew();
DCF(pspew_count, "Number of particles spewed at a time")
{
	if (dc_optional_string_either("help", "--help")) {
		dcf_pspew();
		return;
	}

	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
			dc_printf("Partical count is %i\n", Weapon_particle_spew_count);
			return;
	}

	dc_stuff_int(&Weapon_particle_spew_count);
	
	dc_printf("Partical count set to %i\n", Weapon_particle_spew_count);
}

DCF(pspew_time, "Time between particle spews")
{
	if (dc_optional_string_either("help", "--help")) {
		dcf_pspew();
		return;
	}

	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
		dc_printf("Particle spawn period is %i\n", Weapon_particle_spew_time);
		return;
	}

	dc_stuff_int(&Weapon_particle_spew_time);

	dc_printf("Particle spawn period set to %i\n", Weapon_particle_spew_time);
}

DCF(pspew_vel, "Relative velocity of particles (0.0 - 1.0)")
{
	if (dc_optional_string_either("help", "--help")) {
		dcf_pspew();
		return;
	}

	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
		dc_printf("Particle relative velocity is %f\n", Weapon_particle_spew_vel);
		return;
	}

	dc_stuff_float(&Weapon_particle_spew_vel);

	dc_printf("Particle relative velocity set to %f\n", Weapon_particle_spew_vel);
}

DCF(pspew_size, "Size of spewed particles")
{
	if (dc_optional_string_either("help", "--help")) {
		dcf_pspew();
		return;
	}

	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
		dc_printf("Particle size is %f\n", Weapon_particle_spew_radius);
		return;
	}

	dc_stuff_float(&Weapon_particle_spew_radius);

	dc_printf("Particle size set to %f\n", Weapon_particle_spew_radius);
}

DCF(pspew_life, "Lifetime of spewed particles")
{
	if (dc_optional_string_either("help", "--help")) {
		dcf_pspew();
		return;
	}

	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
		dc_printf("Particle lifetime is %f\n", Weapon_particle_spew_lifetime);
		return;
	}

	dc_stuff_float(&Weapon_particle_spew_lifetime);

	dc_printf("Particle lifetime set to %f\n", Weapon_particle_spew_lifetime);
}

DCF(pspew_scale, "How far away particles are from the weapon path")
{
	if (dc_optional_string_either("help", "--help")) {
		dcf_pspew();
		return;
	}

	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
		dc_printf("Particle scale is %f\n", Weapon_particle_spew_scale);
	}

	dc_stuff_float(&Weapon_particle_spew_scale);

	dc_printf("Particle scale set to %f\n", Weapon_particle_spew_scale);
}

// Help and Status provider
DCF(pspew, "Particle spew help and status provider")
{
	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
		dc_printf("Particle spew settings\n\n");

		dc_printf(" Count   (pspew_count) : %d\n", Weapon_particle_spew_count);
		dc_printf(" Time     (pspew_time) : %d\n", Weapon_particle_spew_time);
		dc_printf(" Velocity  (pspew_vel) : %f\n", Weapon_particle_spew_vel);
		dc_printf(" Size     (pspew_size) : %f\n", Weapon_particle_spew_radius);
		dc_printf(" Lifetime (pspew_life) : %f\n", Weapon_particle_spew_lifetime);
		dc_printf(" Scale   (psnew_scale) : %f\n", Weapon_particle_spew_scale);
		return;
	}

	dc_printf("Available particlar spew commands:\n");
	dc_printf("pspew_count : %s\n", dcmd_pspew_count.help);
	dc_printf("pspew_time  : %s\n", dcmd_pspew_time.help);
	dc_printf("pspew_vel   : %s\n", dcmd_pspew_vel.help);
	dc_printf("pspew_size  : %s\n", dcmd_pspew_size.help);
	dc_printf("pspew_life  : %s\n", dcmd_pspew_life.help);
	dc_printf("pspew_scale : %s\n\n", dcmd_pspew_scale.help);

	dc_printf("To view status of all pspew settings, type in 'pspew --status'.\n");
	dc_printf("Passing '--status' as an argument to any of the individual spew commands will show the status of that variable only.\n\n");

	dc_printf("These commands adjust the various properties of the particle spew system, which is used by weapons when they are fired, are in-flight, and die (either by impact or by end of life time.\n");
	dc_printf("Generally, a large particle count with small size and scale will result in a nice dense particle spew.\n");
	dc_printf("Be advised, this effect is applied to _ALL_ weapons, and as such may drastically reduce framerates on lower powered platforms.\n");
}

/**
 * Return a scale factor for damage which should be applied for 2 collisions
 */
float weapon_get_damage_scale(weapon_info *wip, object *wep, object *target)
{
	weapon *wp;	
	int from_player = 0;
	float total_scale = 1.0f;
	float hull_pct;
	int is_big_damage_ship = 0;

	// Goober5000 - additional sanity (target can be NULL)
	Assert(wip);
	Assert(wep);

	// sanity
	if((wip == NULL) || (wep == NULL) || (target == NULL)){
		return 1.0f;
	}

	// don't scale any damage if its not a weapon	
	if((wep->type != OBJ_WEAPON) || (wep->instance < 0) || (wep->instance >= MAX_WEAPONS)){
		return 1.0f;
	}
	wp = &Weapons[wep->instance];

	// was the weapon fired by the player
	from_player = 0;
	if((wep->parent >= 0) && (wep->parent < MAX_OBJECTS) && (Objects[wep->parent].flags & OF_PLAYER_SHIP)){
		from_player = 1;
	}
		
	// if this is a lockarm weapon, and it was fired unlocked
	if((wip->wi_flags & WIF_LOCKARM) && !(wp->weapon_flags & WF_LOCKED_WHEN_FIRED)){		
		total_scale *= 0.1f;
	}
	
	// if the hit object was a ship and we're doing damage scaling
	if ( (target->type == OBJ_SHIP) &&
		!(The_mission.ai_profile->flags & AIPF_DISABLE_WEAPON_DAMAGE_SCALING) &&
		!(Ship_info[Ships[target->instance].ship_info_index].flags2 & SIF2_DISABLE_WEAPON_DAMAGE_SCALING)
	) {
		ship_info *sip;

		// get some info on the ship
		Assert((target->instance >= 0) && (target->instance < MAX_SHIPS));
		if((target->instance < 0) || (target->instance >= MAX_SHIPS)){
			return total_scale;
		}
		sip = &Ship_info[Ships[target->instance].ship_info_index];

		// get hull pct of the ship currently
		hull_pct = get_hull_pct(target);

		// if it has hit a supercap ship and is not a supercap class weapon
		if((sip->flags & SIF_SUPERCAP) && !(wip->wi_flags & WIF_SUPERCAP)){
			// if the supercap is around 3/4 damage, apply nothing
			if(hull_pct <= 0.75f){
				return 0.0f;
			} else {
				total_scale *= SUPERCAP_DAMAGE_SCALE;
			}
		}

		// determine if this is a big damage ship
		is_big_damage_ship = (sip->flags & SIF_BIG_DAMAGE);

		// if this is a large ship, and is being hit by flak
		if(is_big_damage_ship && (wip->wi_flags & WIF_FLAK)){
			total_scale *= FLAK_DAMAGE_SCALE;
		}
		
		// if the weapon is a small weapon being fired at a big ship
		if( is_big_damage_ship && !(wip->wi_flags & (WIF_HURTS_BIG_SHIPS)) ){

			// if the player is firing it
			if ( from_player && !(The_mission.ai_profile->flags2 & AIPF2_PLAYER_WEAPON_SCALE_FIX)) {
				// if it's a laser weapon
				if(wip->subtype == WP_LASER){
					total_scale *= 0.01f;
				} else {
					total_scale *= 0.05f;
				}
			}

			// scale based on hull
			if(hull_pct > 0.1f){
				total_scale *= hull_pct;
			} else {
				return 0.0f;
			}
		}
	}
	
	return total_scale;
}

void pause_in_flight_sounds()
{
	for (int i = 0; i < MAX_WEAPONS; i++)
	{
		if (Weapons[i].objnum != -1)
		{
			weapon* wp = &Weapons[i];

			if (wp->hud_in_flight_snd_sig >= 0 && snd_is_playing(wp->hud_in_flight_snd_sig))
			{
				// Stop sound, it will be restarted in the first frame after the game is unpaused
				snd_stop(wp->hud_in_flight_snd_sig);
			}
		}
	}
}

void weapon_pause_sounds()
{
	// Pause all beam sounds
	beam_pause_sounds();

	// Pause in-flight sounds
	pause_in_flight_sounds();
}

void weapon_unpause_sounds()
{
	// Pause all beam sounds
	beam_unpause_sounds();
}

void shield_impact_explosion(vec3d *hitpos, object *objp, float radius, int idx) {
	int expl_ani_handle = Weapon_explosions.GetAnim(idx, hitpos, radius);
	particle_create( hitpos, &vmd_zero_vector, 0.0f, radius, PARTICLE_BITMAP_PERSISTENT, expl_ani_handle, -1.0f, objp );
}

void weapon_render(object* obj, draw_list *scene)
{
	int num;
	weapon_info *wip;
	weapon *wp;
	color c;

	MONITOR_INC(NumWeaponsRend, 1);

	Assert(obj->type == OBJ_WEAPON);

	num = obj->instance;
	wp = &Weapons[num];
	wip = &Weapon_info[Weapons[num].weapon_info_index];

	if (wip->wi_flags2 & WIF2_TRANSPARENT) {
		if (wp->alpha_current == -1.0f) {
			wp->alpha_current = wip->alpha_max;
		} else if (wip->alpha_cycle > 0.0f) {
			if (wp->alpha_backward) {
				wp->alpha_current += wip->alpha_cycle;

				if (wp->alpha_current > wip->alpha_max) {
					wp->alpha_current = wip->alpha_max;
					wp->alpha_backward = 0;
				}
			} else {
				wp->alpha_current -= wip->alpha_cycle;

				if (wp->alpha_current < wip->alpha_min) {
					wp->alpha_current = wip->alpha_min;
					wp->alpha_backward = 1;
				}
			}
		}
	}

	switch (wip->render_type)
	{
	case WRT_LASER:
		{
			if(wip->laser_length < 0.0001f)
				return;

			int alpha = 255;
			int framenum = 0;

			if (wip->laser_bitmap.first_frame >= 0) {					
				gr_set_color_fast(&wip->laser_color_1);

				if (wip->laser_bitmap.num_frames > 1) {
					wp->laser_bitmap_frame += flFrametime;

					// Sanity checks
					if (wp->laser_bitmap_frame < 0.0f)
						wp->laser_bitmap_frame = 0.0f;
					if (wp->laser_bitmap_frame > 100.0f)
						wp->laser_bitmap_frame = 0.0f;

					while (wp->laser_bitmap_frame > wip->laser_bitmap.total_time)
						wp->laser_bitmap_frame -= wip->laser_bitmap.total_time;

					framenum = fl2i( (wp->laser_bitmap_frame * wip->laser_bitmap.num_frames) / wip->laser_bitmap.total_time );

					CLAMP(framenum, 0, wip->laser_bitmap.num_frames-1);
				}

				if (wip->wi_flags2 & WIF2_TRANSPARENT)
					alpha = fl2i(wp->alpha_current * 255.0f);

				vec3d headp;

				vm_vec_scale_add(&headp, &obj->pos, &obj->orient.vec.fvec, wip->laser_length);
				wp->weapon_flags &= ~WF_CONSIDER_FOR_FLYBY_SOUND;

				if ( batch_add_laser(wip->laser_bitmap.first_frame + framenum, &headp, wip->laser_head_radius, &obj->pos, wip->laser_tail_radius, alpha, alpha, alpha) ) {
					wp->weapon_flags |= WF_CONSIDER_FOR_FLYBY_SOUND;
				}
			}			

			// maybe draw laser glow bitmap
			if (wip->laser_glow_bitmap.first_frame >= 0) {
				// get the laser color
				weapon_get_laser_color(&c, obj);

				// *Tail point "getting bigger" as well as headpoint isn't being taken into consideration, so
				//  it caused uneven glow between the head and tail, which really shows in big lasers. So...fixed!    -Et1
				vec3d headp2, tailp;

				vm_vec_scale_add(&headp2, &obj->pos, &obj->orient.vec.fvec, wip->laser_length * weapon_glow_scale_l);
				vm_vec_scale_add(&tailp, &obj->pos, &obj->orient.vec.fvec, wip->laser_length * (1 -  weapon_glow_scale_l) );

				framenum = 0;

				if (wip->laser_glow_bitmap.num_frames > 1) {
					wp->laser_glow_bitmap_frame += flFrametime;

					// Sanity checks
					if (wp->laser_glow_bitmap_frame < 0.0f)
						wp->laser_glow_bitmap_frame = 0.0f;
					if (wp->laser_glow_bitmap_frame > 100.0f)
						wp->laser_glow_bitmap_frame = 0.0f;

					while (wp->laser_glow_bitmap_frame > wip->laser_glow_bitmap.total_time)
						wp->laser_glow_bitmap_frame -= wip->laser_glow_bitmap.total_time;

					framenum = fl2i( (wp->laser_glow_bitmap_frame * wip->laser_glow_bitmap.num_frames) / wip->laser_glow_bitmap.total_time );

					CLAMP(framenum, 0, wip->laser_glow_bitmap.num_frames-1);
				}

				if (wip->wi_flags2 & WIF2_TRANSPARENT) {
					alpha = fl2i(wp->alpha_current * 255.0f);
					alpha -= 38; // take 1.5f into account for the normal glow alpha

					if (alpha < 0)
						alpha = 0;
				} else {
					alpha = weapon_glow_alpha;
				}

				batch_add_laser(wip->laser_glow_bitmap.first_frame + framenum, &headp2, wip->laser_head_radius * weapon_glow_scale_f, &tailp, wip->laser_tail_radius * weapon_glow_scale_r, (c.red*alpha)/255, (c.green*alpha)/255, (c.blue*alpha)/255);
			}

			break;
		}

	case WRT_POF:
		{
			model_render_params render_info;

			uint render_flags = MR_NORMAL|MR_IS_MISSILE|MR_NO_LIGHTING|MR_NO_BATCH;

			if (Cmdline_missile_lighting && !(wip->wi_flags2 & WIF2_MR_NO_LIGHTING))
				render_flags &= ~MR_NO_LIGHTING;

			if (wip->wi_flags2 & WIF2_TRANSPARENT) {
				render_info.set_alpha(wp->alpha_current);
				render_flags |= MR_ALL_XPARENT;
			}

			model_clear_instance(wip->model_num);

			if ( (wip->wi_flags & WIF_THRUSTER) && ((wp->thruster_bitmap > -1) || (wp->thruster_glow_bitmap > -1)) ) {
				float ft;
				mst_info mst;

				//	Add noise to thruster geometry.
				ft = 1.0f;		// Always use 1.0f for missiles					
				ft *= (1.0f + frand()/5.0f - 1.0f/10.0f);
				if (ft > 1.0f)
					ft = 1.0f;

				mst.length.xyz.x = ft;
				mst.length.xyz.y = ft;
				mst.length.xyz.z = ft;

				mst.primary_bitmap = wp->thruster_bitmap;
				mst.primary_glow_bitmap = wp->thruster_glow_bitmap;
				mst.glow_rad_factor = wip->thruster_glow_factor;
				mst.glow_noise = wp->thruster_glow_noise;

				render_info.set_thruster_info(mst);

				render_flags |= MR_SHOW_THRUSTERS;
			}


			//don't render local ssm's when they are still in subspace
			if (wp->lssm_stage==3)
				break;

			// start a clip plane
			if ( wp->lssm_stage == 2 ) {
				object *wobj=&Objects[wp->lssm_warp_idx];		//warphole object

				render_info.set_clip_plane(wobj->pos, wobj->orient.vec.fvec);
			}

			render_info.set_flags(render_flags);

			model_render_queue(&render_info, scene, wip->model_num, &obj->orient, &obj->pos);
			wp->weapon_flags |= WF_CONSIDER_FOR_FLYBY_SOUND;

			break;
		}

	default:
		Warning(LOCATION, "Unknown weapon rendering type = %i for weapon %s\n", wip->render_type, wip->name);
	}
}

// Called by hudartillery.cpp after SSMs have been parsed to make sure that $SSM: entries defined in weapons are valid.
void validate_SSM_entries()
{
	int wi;
	SCP_vector<SCP_string>::const_iterator it;
	weapon_info *wip;

	for (it = Delayed_SSM_names.begin(); it != Delayed_SSM_names.end(); ++it) {
		delayed_ssm_data *dat = &Delayed_SSM_data[*it];
		wi = weapon_info_lookup(it->c_str());
		Assertion(wi >= 0, "Trying to validate non-existant weapon '%s'; get a coder!\n", it->c_str());
		wip = &Weapon_info[wi];
		nprintf(("parse", "Starting validation of '%s' [wip->name is '%s'], currently has an SSM_index of %d.\n", it->c_str(), wip->name, wip->SSM_index));
		wip->SSM_index = ssm_info_lookup(dat->ssm_entry.c_str());
		if (wip->SSM_index < 0) {
			Warning(LOCATION, "Unknown SSM entry '%s' in specification for %s (%s:line %d).\n", dat->ssm_entry.c_str(), it->c_str(), dat->filename.c_str(), dat->linenum);
		}
		nprintf(("parse", "Validation complete, SSM_index is %d.\n", wip->SSM_index));
	}

	// This information is no longer relevant, so might as well clear it out.
	Delayed_SSM_data.clear();
	Delayed_SSM_names.clear();

	for (it = Delayed_SSM_indices.begin(); it != Delayed_SSM_indices.end(); ++it) {
		delayed_ssm_index_data *dat = &Delayed_SSM_indices_data[*it];
		wi = weapon_info_lookup(it->c_str());
		Assertion(wi >= 0, "Trying to validate non-existant weapon '%s'; get a coder!\n", it->c_str());
		wip = &Weapon_info[wi];
		nprintf(("parse", "Starting validation of '%s' [wip->name is '%s'], currently has an SSM_index of %d.\n", it->c_str(), wip->name, wip->SSM_index));
		if (wip->SSM_index < -1 || wip->SSM_index >= static_cast<int>(Ssm_info.size())) {
			Warning(LOCATION, "Invalid SSM index '%d' (should be 0-" SIZE_T_ARG ") in specification for %s (%s:line %d).\n", wip->SSM_index, Ssm_info.size() - 1, it->c_str(), dat->filename.c_str(), dat->linenum);
			wip->SSM_index = -1;
		}
		nprintf(("parse", "Validation complete, SSM-index is %d.\n", wip->SSM_index));
	}
}
