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


#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)
{
	int rval;
	uint i;
	lod_checker lod_check;

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

	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");
}

/**
 * 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
			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->damage = 0.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->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->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->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.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)
{
	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) {
			Warning(LOCATION, "Too many weapon classes before '%s'; maximum is %d, so only the first %d will be used", fname, MAX_WEAPON_TYPES, Num_weapon_types);
			
			//Skip the rest of the ships in non-modular tables, since we can't add them.
			if(!replace) {
				if ( !skip_to_start_of_string_either("$Name:", "#End")) {
					Int3();
				}
			}
			return -1;
		}

		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:"))
	{
		stuff_ubyte(&r);
		stuff_ubyte(&g);
		stuff_ubyte(&b);
		gr_init_color( &wip->laser_color_1, r, g, b );
	}

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

	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;
		}
	}
	
	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 (%d) value. Setting to %d.\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->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(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("$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);
	}

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

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

		// # 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:")){
		stuff_int(&wip->SSM_index);
	}// 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=%d, 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)
{
	int rval;

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

	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) < 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) < 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) < 0) {
				continue;
			}
		}
		required_string("#End");
	}

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

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

//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(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_NORMAL|MR_IS_MISSILE|MR_NO_LIGHTING;

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

			if (wip->wi_flags2 & WIF2_TRANSPARENT) {
				model_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;

				model_set_thrust(wip->model_num, &mst);

				render_flags |= MR_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(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);
	}

	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)]);
			}
		}
	}
}

#define	CMEASURE_DETONATE_DISTANCE		40.0f

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

	missile_obj	*mop;

	mop = GET_FIRST(&Missile_obj_list);
	while(mop != END_OF_LIST(&Missile_obj_list)) {
		object	*objp;
		weapon	*wp;

		objp = &Objects[mop->objnum];
		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) < CMEASURE_DETONATE_DISTANCE) {
					if (wp->lifeleft > 0.2f) { 
						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 (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;
					}
					if ((objp->signature != wp->cmeasure_ignore_objnum) && (objp->signature != wp->cmeasure_chase_objnum))
					{
						if (frand() >= chance) {
							wp->cmeasure_ignore_objnum = objp->signature;	//	Don't process this countermeasure again.
						} else  {
							wp->cmeasure_chase_objnum = objp->signature;	//	Don't process this countermeasure again.
						}
					}
				
					if (objp->signature != wp->cmeasure_ignore_objnum)
					{
						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);
						}
					}
				}
			}
		}
	}
}


/**
 * 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 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) &&
            (f2fl(Missiontime - wp->creation_time) > wip->free_flight_time))
        {
            find_homing_object(obj, num);
        }
		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 (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;
	}

	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
			|| Weapon_info[Weapons[hobjp->instance].weapon_info_index].wi_flags3 & WIF3_CMEASURE_ASPECT_HOME_ON;

		// don't home on countermeasures or non-bombs, that's handled elsewhere
		if (((Weapon_info[Weapons[hobjp->instance].weapon_info_index].wi_flags & WIF_CMEASURE) && !home_on_cmeasure))
		{
			break;
		}
		else if (!(Weapon_info[Weapons[hobjp->instance].weapon_info_index].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 && (Weapon_info[Weapons[hobjp->instance].weapon_info_index].wi_flags & WIF_CMEASURE))
			{
				if (dist < CMEASURE_DETONATE_DISTANCE)
				{
					//	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);
						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) &&
            (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 != NULL) && (wp->homing_object->type != 0))
		{
			if(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) {
		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;;
		}

		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);
			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);

			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 ((wp->lssm_stage==4) && (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 (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].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))) {
		// set physics flag
		objp->phys_info.flags |= PF_CONST_VEL;
	}

	wp->start_pos = *pos;
	wp->objnum = objnum;
	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_objnum = -1;
	wp->cmeasure_chase_objnum = -1;
	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 ) {
		objp->radius = model_get_radius(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;
	weapon_info	*wip, *child_wip;

	Assert(objp->type == OBJ_WEAPON);
	Assert((objp->instance >= 0) && (objp->instance < MAX_WEAPONS));

	wp = &Weapons[objp->instance];
	Assert((wp->weapon_info_index >= 0) && (wp->weapon_info_index < MAX_WEAPON_TYPES));
	wip = &Weapon_info[wp->weapon_info_index];

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

    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, &objp->orient.vec.fvec, wip->spawn_info[i].spawn_angle);
	    	} else {
		    	vm_vec_random_cone(&tvec, &objp->orient.vec.fvec, wip->spawn_info[i].spawn_angle);
		    }
		    vm_vec_scale_add(&pos, &objp->pos, &tvec, objp->radius);

		    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 || !(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 == NULL)
				return false;
			if(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 != NULL)
		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){
		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();
}
