2019-10-19 12:13 EDT


View Issue Details Jump to Notes ]
IDProjectCategoryView StatusLast Update
0003103FSSCPtablespublic2015-05-28 17:33
ReporterMageKing17 
Assigned ToMageKing17 
PrioritynormalSeverityfeatureReproducibilityN/A
StatusresolvedResolutionfixed 
Product Version 
Target Version3.7.4Fixed in Version3.7.3 
Summary0003103: Modular ssm.tbl, floating beams, and subspace beam strikes
DescriptionThis one covers quite a few things, because all the changes are more-or-less related.

[1] Makes ssm.tbl modular (with *-ssm.tbm, because... well, what else would you use?), and changes Ssm_info to an SCP_vector and Ssm_strikes to an SCP_list.
[2] Since modular SSMs only make sense if you can refer to them by name everywhere SSMs are referenced (as indices can change as .tbms are added and removed), added special code to process SSM name entries in weapons after SSMs are parsed.
[3] For similar reasons, call-ssm-strike changed to use the name of the class instead of the index; if anyone can think of a good way to allow it to still accept the index, for backwards compatibility, I'd like to know; otherwise, call-ssm-strike is new enough that I'm willing to break it for the sake of making it make sense (in addition, it now uses OPF_SSM_CLASS, which leads us to our next change...).
[4] Makes FRED2 actually call ssm_init(), so now OPF_SSM_CLASS does something.
[5] Makes ship-tag actually use OPF_SSM_CLASS.
[6] Because the SSM code allows non-missile weapons, went ahead and added special code to handle an "SSM" strike that fires beams (an SSB strike, if you will).
[7] In order for the above to work, made it possible for beams to exist without a firing turret.
[8] Since the above is now possible, added a beam-create SEXP (mirroring weapon-create) to fire a beam from an arbitrary point in space.
[9] Made a few ssm.tbl items optional and added "+Shape: Sphere" to allow the entry points to be randomly placed on a sphere around the target, instead of a circle (uses new function vm_vec_random_in_sphere(), which calls vm_vec_random_in_circle() and then rotates it again).

...I think that covers everything, yup.

These items are not listed in order of priority, effort, or when I did them, either. The actual impetus for this patch was [6], [7] took the most time, and while [1] was the first thing I did, [2] was the second-to-last thing coded.

Make that third-to-last, since the new version of the patch. Also, [3], [4], and [5] were committed in isolation in r11078.
Additional InformationTest build here: http://deviance.duckish.net/downloads/fs2_fred2_open_ssb_test.7z
TagsNo tags attached.
Attached Files
  • ? file icon test-shp.tbm (220 bytes) 2014-10-31 17:50
  • ? file icon test-ssm.tbm (439 bytes) 2014-10-31 17:50
  • ? file icon test-wep.tbm (11,695 bytes) 2014-10-31 17:50
  • ? file icon TAGTest.fs2 (4,342 bytes) 2014-10-31 17:50
  • patch file icon subspace_beams.patch (59,568 bytes) 2014-12-22 23:12 -
    Index: code/fred2/sexp_tree.cpp
    ===================================================================
    --- code/fred2/sexp_tree.cpp	(revision 11202)
    +++ code/fred2/sexp_tree.cpp	(working copy)
    @@ -2797,7 +2797,7 @@
     			return (sexp_variable_count() > 0) ? 1 : 0;
     
     		case OPF_SSM_CLASS:
    -			return (Ssm_info_count > 0) ? 1 : 0;
    +			return (Ssm_info.size() > 0) ? 1 : 0;
     
     		case OPF_MISSION_MOOD:
     			return Builtin_moods.empty() ? 0 : 1;
    @@ -5058,12 +5058,12 @@
     
     sexp_list_item *sexp_tree::get_listing_opf_ssm_class()
     {
    -	int i;
    +	SCP_vector<ssm_info>::const_iterator it;
     	sexp_list_item head;
     
    -	for (i=0; i<Ssm_info_count; i++)
    +	for (it = Ssm_info.begin(); it != Ssm_info.end(); ++it)
     	{
    -		head.add_data(Ssm_info[i].name);
    +		head.add_data(it->name);
     	}
     
     	return head.next;
    Index: code/hud/hudartillery.cpp
    ===================================================================
    --- code/hud/hudartillery.cpp	(revision 11202)
    +++ code/hud/hudartillery.cpp	(working copy)
    @@ -13,6 +13,7 @@
     #include "hud/hudartillery.h"
     #include "parse/parselo.h"
     #include "weapon/weapon.h"
    +#include "weapon/beam.h"
     #include "math/vecmat.h"
     #include "globalincs/linklist.h"
     #include "io/timer.h"
    @@ -37,106 +38,155 @@
     // test code for subspace missile strike -------------------------------------------
     
     // ssm_info, like ship_info etc.
    -int Ssm_info_count = 0;
    -ssm_info Ssm_info[MAX_SSM_TYPES];
    +SCP_vector<ssm_info> Ssm_info;
     
    -// list of active/free strikes
    -int Num_ssm_strikes = 0;
    -ssm_strike Ssm_strikes[MAX_SSM_STRIKES];
    -ssm_strike Ssm_free_list;
    -ssm_strike Ssm_used_list;
    +// list of active strikes
    +SCP_list<ssm_strike> Ssm_strikes;
     
     // Goober5000
    -int ssm_info_lookup(char *name)
    +int ssm_info_lookup(const char *name)
     {
     	if(name == NULL)
     		return -1;
     
    -	for (int i = 0; i < Ssm_info_count; i++)
    -		if (!stricmp(name, Ssm_info[i].name))
    -			return i;
    +	for (SCP_vector<ssm_info>::const_iterator it = Ssm_info.begin(); it != Ssm_info.end(); ++it)
    +		if (!stricmp(name, it->name))
    +			return it - Ssm_info.begin();
     
     	return -1;
     }
     
    -// game init
    -void ssm_init()
    -{	
    -	int rval;
    -	ssm_info bogus, *s;
    +void parse_ssm(const char *filename)
    +{
     	char weapon_name[NAME_LENGTH];
     
    -	if ((rval = setjmp(parse_abort)) != 0) {
    -		mprintf(("TABLES: Unable to parse '%s'!  Error code = %i.\n", "ssm.tbl", rval));
    +	int err_code;
    +	if ((err_code = setjmp(parse_abort)) != 0) {
    +		mprintf(("TABLES: Unable to parse '%s'!  Error code = %i.\n", filename, err_code));
     		return;
     	}
     
    -	read_file_text("ssm.tbl", CF_TYPE_TABLES);
    +	read_file_text(filename, CF_TYPE_TABLES);
     	reset_parse();
     
     	// parse the table
    -	Ssm_info_count = 0;
    -	while(!optional_string("#end")){
    -		// another ssm definition
    -		if(optional_string("$SSM:")){
    -			// pointer to info struct
    -			if(Ssm_info_count >= MAX_SSM_TYPES){
    -				s = &bogus;
    -			} else {
    -				s = &Ssm_info[Ssm_info_count];
    -			}
    +	while(required_string_either("#end", "$SSM:")) {
    +		required_string("$SSM:");
    +		ssm_info s;
     
    -			// name
    -			stuff_string(s->name, F_NAME, NAME_LENGTH);
    +		// name
    +		stuff_string(s.name, F_NAME, NAME_LENGTH);
     
    -			// stuff data
    -			required_string("+Weapon:");
    -			stuff_string(weapon_name, F_NAME, NAME_LENGTH);
    -			required_string("+Count:");
    -			stuff_int(&s->count);
    -			required_string("+WarpRadius:");
    -			stuff_float(&s->warp_radius);
    -			required_string("+WarpTime:");
    -			stuff_float(&s->warp_time);
    +		// stuff data
    +		required_string("+Weapon:");
    +		stuff_string(weapon_name, F_NAME, NAME_LENGTH);
    +		if (optional_string("+Count:"))
    +			stuff_int(&s.count);
    +		else
    +			s.count = 1;
    +		required_string("+WarpRadius:");
    +		stuff_float(&s.warp_radius);
    +		if (optional_string("+WarpTime:")) {
    +			stuff_float(&s.warp_time);
     			// According to fireballs.cpp, "Warp lifetime must be at least 4 seconds!"
    -			if ( (s->warp_time) < 4.0f) {
    +			if ( (s.warp_time) < 4.0f) {
     				// So let's warn them before they try to use it, shall we?
    -				Warning(LOCATION, "Expected a '+WarpTime:' value equal or greater than 4.0, found '%f' in weapon '%s'.\n Setting to 4.0, please check and set to a number 4.0 or greater!\n", s->warp_time, weapon_name);
    +				Warning(LOCATION, "Expected a '+WarpTime:' value equal or greater than 4.0, found '%f' in weapon '%s'.\n Setting to 4.0, please check and set to a number 4.0 or greater!\n", s.warp_time, weapon_name);
     				// And then make the Assert obsolete -- Zacam
    -				s->warp_time = 4.0f;
    +				s.warp_time = 4.0f;
     			}
    -			required_string("+Radius:");
    -			stuff_float(&s->radius);
    -			required_string("+Offset:");
    -			stuff_float(&s->offset);
    -			if (optional_string("+HUD Message:")) 
    -				stuff_boolean(&s->send_message);
    -			else
    -				s->send_message = true;
    -			if (optional_string("+Custom Message:")) {
    -				stuff_string(s->message, F_NAME, NAME_LENGTH);
    -				s->use_custom_message = true;
    +		} else {
    +			s.warp_time = 4.0f;
    +		}
    +		required_string("+Radius:");
    +		stuff_float(&s.radius);
    +		if (optional_string("+Offset:"))
    +			stuff_float(&s.offset);
    +		else
    +			s.offset = 0.0f;
    +		if (optional_string("+Shape:")) {
    +			switch(required_string_one_of(3, "Point", "Circle", "Sphere")) {
    +			case 0:
    +				required_string("Point");
    +				s.shape = SSM_SHAPE_POINT;
    +				break;
    +			case 1:
    +				required_string("Circle");
    +			case -1:	// If we're ignoring parse errors and can't identify the shape, go with a circle.
    +				s.shape = SSM_SHAPE_CIRCLE;
    +				break;
    +			case 2:
    +				required_string("Sphere");
    +				s.shape = SSM_SHAPE_SPHERE;
    +				break;
    +			default:
    +				Assertion(false, "Impossible return value from required_string_one_of(); get a coder!\n");
     			}
    -			parse_sound("+Alarm Sound:", &s->sound_index, s->name);
    +		} else {
    +			s.shape = SSM_SHAPE_CIRCLE;
    +		}
    +		if (optional_string("+HUD Message:")) 
    +			stuff_boolean(&s.send_message);
    +		else
    +			s.send_message = true;
    +		if (optional_string("+Custom Message:")) {
    +			stuff_string(s.message, F_NAME, NAME_LENGTH);
    +			s.use_custom_message = true;
    +		} else {
    +			s.use_custom_message = false;
    +		}
    +		s.sound_index = -1;
    +		parse_sound("+Alarm Sound:", &s.sound_index, s.name);
     
    -			// see if we have a valid weapon
    -			s->weapon_info_index = -1;
    -			s->weapon_info_index = weapon_info_lookup(weapon_name);
    -			if(s->weapon_info_index >= 0){
    -				// valid
    -				Ssm_info_count++;
    +		// see if we have a valid weapon
    +		s.weapon_info_index = weapon_info_lookup(weapon_name);
    +		if(s.weapon_info_index >= 0){
    +			// valid
    +			int existing = ssm_info_lookup(s.name);
    +			if (existing >= 0) {	// Redefine the existing entry instead of adding a duplicate.
    +				Ssm_info[existing] = s;
    +			} else {
    +				Ssm_info.push_back(s);
     			}
     		}
     	}
     }
     
    +// game init
    +void ssm_init()
    +{	
    +	if (cf_exists_full("ssm.tbl", CF_TYPE_TABLES)) {
    +		mprintf(("TABLES => Starting parse of 'ssm.tbl'...\n"));
    +		parse_ssm("ssm.tbl");
    +	}
    +	parse_modular_table(NOX("*-ssm.tbm"), parse_ssm);
    +
    +	// Now that we've populated Ssm_info, let's validate weapon $SSM: entries.
    +	validate_SSM_entries();
    +}
    +
     void ssm_get_random_start_pos(vec3d *out, vec3d *start, matrix *orient, int ssm_index)
     {
     	vec3d temp;
     	ssm_info *s = &Ssm_info[ssm_index];
     
    -	// get a random vector in the circle of the firing plane
    -	vm_vec_random_in_circle(&temp, start, orient, s->radius, 1);
    +	switch (s->shape) {
    +	case SSM_SHAPE_SPHERE:
    +		// get a random vector in a sphere around the target
    +		vm_vec_random_in_sphere(&temp, start, orient, s->radius, 1);
    +		break;
    +	case SSM_SHAPE_CIRCLE:
    +		// get a random vector in the circle of the firing plane
    +		vm_vec_random_in_circle(&temp, start, orient, s->radius, 1);
    +		break;
    +	case SSM_SHAPE_POINT:
    +		// boooring
    +		vm_vec_scale_add(&temp, start, &orient->vec.fvec, s->radius);
    +		break;
    +	default:
    +		Assertion(false, "Unknown shape '%d' in SSM type #%d ('%s'). This should not be possible; get a coder!\n", s->shape, ssm_index, s->name);
    +		break;
    +	}
     
     	// offset it a bit
     	vm_vec_scale_add(out, &temp, &orient->vec.fvec, s->offset);
    @@ -145,32 +195,15 @@
     // level init
     void ssm_level_init()
     {
    -	int i;
    -
    -	Num_ssm_strikes = 0;
    -	list_init( &Ssm_free_list );
    -	list_init( &Ssm_used_list );
    -
    -	// Link all object slots into the free list
    -	for (i=0; i<MAX_SSM_STRIKES; i++)	{
    -		list_append(&Ssm_free_list, &Ssm_strikes[i] );
    -	}
     }
     
     // start a subspace missile effect
     void ssm_create(object *target, vec3d *start, int ssm_index, ssm_firing_info *override, int team)
     {	
    -	ssm_strike *ssm;		
    +	ssm_strike ssm;
     	matrix dir;
    -	int idx;
    +	int idx, count;
     
    -	if (Num_ssm_strikes >= MAX_SSM_STRIKES ) {
    -		#ifndef NDEBUG
    -		mprintf(("Ssm creation failed - too many ssms!\n" ));
    -		#endif
    -		return;
    -	}
    -
     	// sanity
     	Assert(target != NULL);
     	if(target == NULL){
    @@ -180,28 +213,17 @@
     	if(start == NULL){
     		return;
     	}
    -	if((ssm_index < 0) || (ssm_index >= MAX_SSM_TYPES)){
    +	if((ssm_index < 0) || ssm_index >= static_cast<int>(Ssm_info.size())){
     		return;
     	}
     
    -	// Find next available trail
    -	ssm = GET_FIRST(&Ssm_free_list);
    -	Assert( ssm != &Ssm_free_list );		// shouldn't have the dummy element
    +	// Init the ssm data
     
    -	// remove trailp from the free list
    -	list_remove( &Ssm_free_list, ssm );
    -	
    -	// insert trailp onto the end of used list
    -	list_append( &Ssm_used_list, ssm );
    +	count = Ssm_info[ssm_index].count;
     
    -	// increment counter
    -	Num_ssm_strikes++;	
    -
    -	// Init the ssm data
    -
     	// override in multiplayer
     	if(override != NULL){
    -		ssm->sinfo = *override;
    +		ssm.sinfo = *override;
     	}
     	// single player or the server
     	else {
    @@ -208,21 +230,34 @@
     		// forward orientation
     		vec3d temp;
     
    -        vm_vec_sub(&temp, &target->pos, start);
    -        vm_vec_normalize(&temp);
    +		vm_vec_sub(&temp, &target->pos, start);
    +		vm_vec_normalize(&temp);
     
     		vm_vector_2_matrix(&dir, &temp, NULL, NULL);
     
     		// stuff info
    -		ssm->sinfo.ssm_index = ssm_index;
    -		ssm->sinfo.target = target;
    -        ssm->sinfo.ssm_team = team;
    +		ssm.sinfo.ssm_index = ssm_index;
    +		ssm.sinfo.target = target;
    +		ssm.sinfo.ssm_team = team;
     
    -		for(idx=0; idx<Ssm_info[ssm_index].count; idx++){
    -			ssm->sinfo.delay_stamp[idx] = timestamp(200 + (int)frand_range(-199.0f, 1000.0f));
    -			ssm_get_random_start_pos(&ssm->sinfo.start_pos[idx], start, &dir, ssm_index);
    +		// Instead of pushing them on one at a time, let's just grab all the memory we'll need at once
    +		// (as a side effect, don't need to change the old code from when these were arrays)
    +		ssm.sinfo.delay_stamp.resize(count);
    +		ssm.sinfo.start_pos.resize(count);
    +
    +		for(idx=0; idx<count; idx++){
    +			ssm.sinfo.delay_stamp[idx] = timestamp(200 + (int)frand_range(-199.0f, 1000.0f));
    +			ssm_get_random_start_pos(&ssm.sinfo.start_pos[idx], start, &dir, ssm_index);
     		}
     
    +		ssm_info *si = &Ssm_info[ssm_index];
    +		weapon_info *wip = &Weapon_info[si->weapon_info_index];
    +		if (wip->wi_flags & WIF_BEAM) {
    +			ssm.sinfo.duration = ((si->warp_time - ((wip->b_info.beam_warmup / 1000.0f) + wip->b_info.beam_life + (wip->b_info.beam_warmdown / 1000.0f))) / 2.0f) / si->warp_time;
    +		} else {
    +			ssm.sinfo.duration = 0.5f;
    +		}
    +
     		// if we're the server, send a packet
     		if(MULTIPLAYER_MASTER){
     			//
    @@ -229,11 +264,10 @@
     		}
     	}
     
    -	// clear timestamps, handles, etc
    -	for(idx=0; idx<MAX_SSM_COUNT; idx++){
    -		ssm->done_flags[idx] = 0;
    -		ssm->fireballs[idx] = -1;
    -	}
    +	ssm.done_flags.clear();
    +	ssm.done_flags.resize(count);
    +	ssm.fireballs.clear();
    +	ssm.fireballs.resize(count, -1);
     	
     	if(Ssm_info[ssm_index].send_message) {
     		if (!Ssm_info[ssm_index].use_custom_message)
    @@ -244,21 +278,13 @@
     	if (Ssm_info[ssm_index].sound_index >= 0) {
     		snd_play(&Snds[Ssm_info[ssm_index].sound_index]);
     	}
    +	Ssm_strikes.push_back(ssm);
     }
     
     // delete a finished ssm effect
    -void ssm_delete(ssm_strike *ssm)
    +void ssm_delete(SCP_list<ssm_strike>::iterator ssm)
     {
    -	// remove objp from the used list
    -	list_remove( &Ssm_used_list, ssm );
    -
    -	// add objp to the end of the free
    -	list_append( &Ssm_free_list, ssm );
    -
    -	// decrement counter
    -	Num_ssm_strikes--;
    -
    -	nprintf(("General", "Recycling SSM, %d left", Num_ssm_strikes));
    +	Ssm_strikes.erase(ssm);
     }
     
     // process subspace missile stuff
    @@ -265,13 +291,13 @@
     void ssm_process()
     {
     	int idx, finished;
    -	ssm_strike *moveup, *next_one;
    +	SCP_list<ssm_strike>::iterator moveup, eraser;
     	ssm_info *si;
    -    int weapon_objnum;
    +	int weapon_objnum;
     	
     	// process all strikes	
    -	moveup=GET_FIRST(&Ssm_used_list);
    -	while ( moveup!=END_OF_LIST(&Ssm_used_list) )	{		
    +	moveup = Ssm_strikes.begin();
    +	while ( moveup != Ssm_strikes.end() ) {
     		// get the type
     		if(moveup->sinfo.ssm_index < 0){
     			continue;
    @@ -287,29 +313,50 @@
     
     				// if he already has the fireball effect
     				if(moveup->fireballs[idx] >= 0){
    -					// if the warp effect is half done, fire the missile
    -					if((1.0f - fireball_lifeleft_percent(&Objects[moveup->fireballs[idx]])) >= 0.5f){
    -						// get an orientation
    -						vec3d temp;
    -						matrix orient;
    +					if ((1.0f - fireball_lifeleft_percent(&Objects[moveup->fireballs[idx]])) >= moveup->sinfo.duration) {
    +						weapon_info *wip = &Weapon_info[si->weapon_info_index];
    +						// are we a beam? -MageKing17
    +						if (wip->wi_flags & WIF_BEAM) {
    +							beam_fire_info fire_info;
    +							memset(&fire_info, 0, sizeof(beam_fire_info));
     
    -						vm_vec_sub(&temp, &moveup->sinfo.target->pos, &moveup->sinfo.start_pos[idx]);
    -						vm_vec_normalize(&temp);
    -						vm_vector_2_matrix(&orient, &temp, NULL, NULL);
    +							fire_info.accuracy = 0.000001f;		// this will guarantee a hit
    +							fire_info.shooter = NULL;
    +							fire_info.turret = NULL;
    +							fire_info.target = moveup->sinfo.target;
    +							fire_info.target_subsys = NULL;
    +							fire_info.bfi_flags |= BFIF_FLOATING_BEAM;
    +							fire_info.starting_pos = moveup->sinfo.start_pos[idx];
    +							fire_info.beam_info_index = si->weapon_info_index;
    +							fire_info.team = static_cast<char>(moveup->sinfo.ssm_team);
     
    -						// fire the missile and flash the screen
    -						weapon_objnum = weapon_create(&moveup->sinfo.start_pos[idx], &orient, si->weapon_info_index, -1, -1, 1);
    +							// fire the beam
    +							beam_fire(&fire_info);
     
    -						if (weapon_objnum >= 0) {
    -							Weapons[Objects[weapon_objnum].instance].team = moveup->sinfo.ssm_team;
    -							Weapons[Objects[weapon_objnum].instance].homing_object = moveup->sinfo.target;
    -							Weapons[Objects[weapon_objnum].instance].target_sig = moveup->sinfo.target->signature;
    +							moveup->done_flags[idx] = true;
    +						} else {
    +							// get an orientation
    +							vec3d temp;
    +							matrix orient;
    +
    +							vm_vec_sub(&temp, &moveup->sinfo.target->pos, &moveup->sinfo.start_pos[idx]);
    +							vm_vec_normalize(&temp);
    +							vm_vector_2_matrix(&orient, &temp, NULL, NULL);
    +
    +							// fire the missile and flash the screen
    +							weapon_objnum = weapon_create(&moveup->sinfo.start_pos[idx], &orient, si->weapon_info_index, -1, -1, 1);
    +
    +							if (weapon_objnum >= 0) {
    +								Weapons[Objects[weapon_objnum].instance].team = moveup->sinfo.ssm_team;
    +								Weapons[Objects[weapon_objnum].instance].homing_object = moveup->sinfo.target;
    +								Weapons[Objects[weapon_objnum].instance].target_sig = moveup->sinfo.target->signature;
    +							}
    +
    +							// this makes this particular missile done
    +							moveup->done_flags[idx] = true;
     						}
    -
    -						// this makes this particular missile done
    -						moveup->done_flags[idx] = 1;
     					}
    -				} 
    +				}
     				// maybe create his warpin effect
     				else if((moveup->sinfo.delay_stamp[idx] >= 0) && timestamp_elapsed(moveup->sinfo.delay_stamp[idx])){
     					// get an orientation
    @@ -324,14 +371,14 @@
     			}
     		}
     		if(finished){
    -			next_one = GET_NEXT(moveup);			
    -			ssm_delete(moveup);															
    -			moveup = next_one;
    +			eraser = moveup;
    +			++moveup;
    +			ssm_delete(eraser);
     			continue;
     		}
     		
    -		moveup=GET_NEXT(moveup);
    -	}	
    +		++moveup;
    +	}
     }
     
     
    Index: code/hud/hudartillery.h
    ===================================================================
    --- code/hud/hudartillery.h	(revision 11202)
    +++ code/hud/hudartillery.h	(working copy)
    @@ -18,10 +18,12 @@
     // -----------------------------------------------------------------------------------------------------------------------
     // ARTILLERY DEFINES/VARS
     //
    -#define MAX_SSM_TYPES			10
    -#define MAX_SSM_STRIKES			10
    -#define MAX_SSM_COUNT			10
     
    +// SSM shapes
    +#define SSM_SHAPE_POINT			0
    +#define SSM_SHAPE_CIRCLE		1
    +#define SSM_SHAPE_SPHERE		2
    +
     // global ssm types
     typedef struct ssm_info {
     	char			name[NAME_LENGTH];				// strike name
    @@ -35,32 +37,31 @@
     	bool		use_custom_message;
     	bool		send_message;
     	int			sound_index;
    +	int			shape;
     } ssm_info;
     
     // creation info for the strike (useful for multiplayer)
     typedef struct ssm_firing_info {
    -	int     delay_stamp[MAX_SSM_COUNT];	    // timestamps
    -	vec3d   start_pos[MAX_SSM_COUNT];       // start positions
    +	SCP_vector<int>     delay_stamp;	    // timestamps
    +	SCP_vector<vec3d>   start_pos;       // start positions
     	
     	int             ssm_index;							// index info ssm_info array
     	class object*  target;								// target for the strike
         int             ssm_team;                           // team that fired the ssm.
    +	float			duration;							// how far into the warp effect to fire
     } ssm_firing_info;
     
     // the strike itself
     typedef struct ssm_strike {
    -	int			fireballs[MAX_SSM_COUNT];		// warpin effect fireballs
    -	int			done_flags[MAX_SSM_COUNT];		// when we've fired off the individual missiles
    +	SCP_vector<int>			fireballs;		// warpin effect fireballs
    +	SCP_vector<bool>		done_flags;		// when we've fired off the individual missiles
     	
     	// this is the info that controls how the strike behaves (just like for beam weapons)
     	ssm_firing_info		sinfo;
    -
    -	ssm_strike	*next, *prev;						// for list
     } ssm_strike;
     
     
    -extern int Ssm_info_count;
    -extern ssm_info Ssm_info[MAX_SSM_TYPES];
    +extern SCP_vector<ssm_info> Ssm_info;
     
     
     // -----------------------------------------------------------------------------------------------------------------------
    @@ -80,6 +81,6 @@
     void ssm_create(object *target, vec3d *start, int ssm_index, ssm_firing_info *override, int team);
     
     // Goober5000
    -extern int ssm_info_lookup(char *name);
    +extern int ssm_info_lookup(const char *name);
     
     #endif
    Index: code/math/vecmat.cpp
    ===================================================================
    --- code/math/vecmat.cpp	(revision 11202)
    +++ code/math/vecmat.cpp	(working copy)
    @@ -2735,7 +2735,7 @@
     
     // given a start vector, an orientation and a radius, give a point on the plane of the circle
     // if on_edge is 1, the point is on the very edge of the circle
    -void vm_vec_random_in_circle(vec3d *out, vec3d *in, matrix *orient, float radius, int on_edge)
    +void vm_vec_random_in_circle(vec3d *out, vec3d *in, matrix *orient, const float radius, const int on_edge)
     {
     	vec3d temp;
     
    @@ -2746,6 +2746,15 @@
     	vm_rot_point_around_line(out, &temp, fl_radians(frand_range(0.0f, 359.0f)), in, &orient->vec.fvec);
     }
     
    +// given a start vector, an orientation, and a radius, give a point in a spherical volume
    +// if on_edge is 1, the point is on the very edge of the sphere
    +void vm_vec_random_in_sphere(vec3d *out, vec3d *in, matrix *orient, const float radius, const int on_edge)
    +{
    +	vec3d temp;
    +	vm_vec_random_in_circle(&temp, in, orient, radius, on_edge);
    +	vm_rot_point_around_line(out, &temp, fl_radians(frand_range(0.0f, 359.0f)), in, &orient->vec.rvec);
    +}
    +
     // find the nearest point on the line to p. if dist is non-NULL, it is filled in
     // returns 0 if the point is inside the line segment, -1 if "before" the line segment and 1 ir "after" the line segment
     int vm_vec_dist_to_line(vec3d *p, vec3d *l0, vec3d *l1, vec3d *nearest, float *dist)
    Index: code/math/vecmat.h
    ===================================================================
    --- code/math/vecmat.h	(revision 11202)
    +++ code/math/vecmat.h	(working copy)
    @@ -502,8 +502,12 @@
     
     // given a start vector, an orientation and a radius, give a point on the plane of the circle
     // if on_edge is 1, the point is on the very edge of the circle
    -void vm_vec_random_in_circle(vec3d *out, vec3d *in, matrix *orient, float radius, int on_edge);
    +void vm_vec_random_in_circle(vec3d *out, vec3d *in, matrix *orient, const float radius, const int on_edge);
     
    +// given a start vector, an orientation, and a radius, give a point in a spherical volume
    +// if on_edge is 1, the point is on the very edge of the sphere
    +void vm_vec_random_in_sphere(vec3d *out, vec3d *in, matrix *orient, const float radius, const int on_edge);
    +
     // find the nearest point on the line to p. if dist is non-NULL, it is filled in
     // returns 0 if the point is inside the line segment, -1 if "before" the line segment and 1 ir "after" the line segment
     int vm_vec_dist_to_line(vec3d *p, vec3d *l0, vec3d *l1, vec3d *nearest, float *dist);
    Index: code/parse/sexp.cpp
    ===================================================================
    --- code/parse/sexp.cpp	(revision 11202)
    +++ code/parse/sexp.cpp	(working copy)
    @@ -479,6 +479,7 @@
     	//Beams and Turrets Sub-Category
     	{ "fire-beam",						OP_BEAM_FIRE,							3,	5,			SEXP_ACTION_OPERATOR,	},
     	{ "fire-beam-at-coordinates",		OP_BEAM_FIRE_COORDS,					5,	9,			SEXP_ACTION_OPERATOR,	},
    +	{ "beam-create",					OP_BEAM_FLOATING_FIRE,					7,	14,			SEXP_ACTION_OPERATOR,	},
     	{ "beam-free",						OP_BEAM_FREE,							2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
     	{ "beam-free-all",					OP_BEAM_FREE_ALL,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
     	{ "beam-lock",						OP_BEAM_LOCK,							2,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
    @@ -16943,6 +16944,100 @@
     	}
     }	
     
    +void sexp_beam_floating_fire(int n)
    +{
    +	int sindex;
    +	beam_fire_info fire_info;
    +	memset(&fire_info, 0, sizeof(beam_fire_info));
    +	fire_info.accuracy = 0.000001f;							// this will guarantee a hit
    +	fire_info.bfi_flags |= BFIF_FLOATING_BEAM;
    +	fire_info.turret = NULL;		// A free-floating beam isn't fired from a subsystem.
    +
    +	fire_info.beam_info_index = weapon_info_lookup(CTEXT(n));
    +	n = CDR(n);
    +	if (fire_info.beam_info_index < 0)
    +	{
    +		Warning(LOCATION, "Invalid weapon class passed to beam-create; weapon type '%s' does not exist!\n", CTEXT(n));
    +		return;
    +	}
    +	if (!(Weapon_info[fire_info.beam_info_index].wi_flags & WIF_BEAM)) {
    +		Warning(LOCATION, "Invalid weapon class passed to beam-create; weapon type '%s' is not a beam!\n", CTEXT(n));
    +		return;
    +	}
    +
    +	fire_info.shooter = NULL;
    +	if (stricmp(CTEXT(n), SEXP_NONE_STRING))
    +	{
    +		sindex = ship_name_lookup(CTEXT(n));
    +
    +		if (sindex >= 0)
    +			fire_info.shooter = &Objects[Ships[sindex].objnum];
    +	}
    +	n = CDR(n);
    +
    +	fire_info.team = static_cast<char>(iff_lookup(CTEXT(n)));
    +	n = CDR(n);
    +
    +	fire_info.starting_pos.xyz.x = static_cast<float>(eval_num(n));
    +	n = CDR(n);
    +	fire_info.starting_pos.xyz.y = static_cast<float>(eval_num(n));
    +	n = CDR(n);
    +	fire_info.starting_pos.xyz.z = static_cast<float>(eval_num(n));
    +	n = CDR(n);
    +
    +	fire_info.target = NULL;
    +	fire_info.target_subsys = NULL;
    +
    +	sindex = -1;
    +	if (stricmp(CTEXT(n), SEXP_NONE_STRING))
    +	{
    +		sindex = ship_name_lookup(CTEXT(n));
    +
    +		if (sindex >= 0)
    +			fire_info.target = &Objects[Ships[sindex].objnum];
    +	} else {
    +		fire_info.bfi_flags |= BFIF_TARGETING_COORDS;
    +	}
    +	n = CDR(n);
    +
    +	if (n >= 0 && stricmp(CTEXT(n), SEXP_NONE_STRING))
    +	{
    +		if (sindex >= 0)
    +			fire_info.target_subsys = ship_get_subsys(&Ships[sindex], CTEXT(n));
    +
    +		n = CDR(n);
    +	}
    +
    +	if (n >= 0) {
    +		fire_info.target_pos1.xyz.x = fire_info.target_pos2.xyz.x = static_cast<float>(eval_num(n));
    +		n = CDR(n);
    +	}
    +	if (n >= 0) {
    +		fire_info.target_pos1.xyz.y = fire_info.target_pos2.xyz.y = static_cast<float>(eval_num(n));
    +		n = CDR(n);
    +	}
    +	if (n >= 0) {
    +		fire_info.target_pos1.xyz.z = fire_info.target_pos2.xyz.z = static_cast<float>(eval_num(n));
    +		n = CDR(n);
    +	}
    +
    +	if (n >= 0) {
    +		fire_info.target_pos2.xyz.x = static_cast<float>(eval_num(n));
    +		n = CDR(n);
    +	}
    +	if (n >= 0) {
    +		fire_info.target_pos2.xyz.y = static_cast<float>(eval_num(n));
    +		n = CDR(n);
    +	}
    +	if (n >= 0) {
    +		fire_info.target_pos2.xyz.z = static_cast<float>(eval_num(n));
    +		n = CDR(n);
    +	}
    +
    +	// create the weapon
    +	beam_fire(&fire_info);
    +}
    +
     void sexp_beam_free(int node)
     {
     	int sindex;
    @@ -25402,6 +25497,7 @@
     		case OP_SET_VARIABLE_BY_INDEX:
     		case OP_BEAM_FIRE:
     		case OP_BEAM_FIRE_COORDS:
    +		case OP_BEAM_FLOATING_FIRE:
     		case OP_BEAM_FREE:
     		case OP_BEAM_FREE_ALL:
     		case OP_BEAM_LOCK:
    @@ -26939,6 +27035,18 @@
     					return OPF_NUMBER;
     			}
     
    +		case OP_BEAM_FLOATING_FIRE:
    +			if (argnum == 0)
    +				return OPF_WEAPON_NAME;
    +			else if (argnum == 1 || argnum == 6)
    +				return OPF_SHIP_OR_NONE;
    +			else if (argnum == 2)
    +				return OPF_IFF;
    +			else if (argnum == 7)
    +				return OPF_SUBSYSTEM_OR_NONE;
    +			else
    +				return OPF_NUMBER;
    +
     		case OP_IS_TAGGED:
     			return OPF_SHIP;
     
    @@ -29231,6 +29339,7 @@
     
     		case OP_BEAM_FIRE:
     		case OP_BEAM_FIRE_COORDS:
    +		case OP_BEAM_FLOATING_FIRE:
     		case OP_BEAM_FREE:
     		case OP_BEAM_FREE_ALL:
     		case OP_BEAM_LOCK:
    @@ -31843,6 +31952,23 @@
     		"\t8:\tsecond y coordinate to be targeted (optional; only used for slash beams)\r\n"
     		"\t9:\tsecond z coordinate to be targeted (optional; only used for slash beams)\r\n" },
     
    +	{ OP_BEAM_FLOATING_FIRE, "beam-create\r\n"
    +		"\tFire a beam weapon from the specified coordinates to the specified target. Not compatible with multiplayer.\r\n"
    +		"\t1:\tBeam weapon to fire\r\n"
    +		"\t2:\tParent ship (for kill credit, if applicable; can be none)\r\n"
    +		"\t3:\tTeam for this beam to be on (related to difficulty-based damage)\r\n"
    +		"\t4:\tX coordinate to fire from\r\n"
    +		"\t5:\tY coordinate to fire from\r\n"
    +		"\t6:\tZ coordinate to fire from\r\n"
    +		"\t7:\tTarget ship (can be none)\r\n"
    +		"\t8:\tTarget subsystem (optional, can be none)\r\n"
    +		"\t9:\tX coordinate to fire at (optional)\r\n"
    +		"\t10:\tY coordinate to fire at (optional)\r\n"
    +		"\t11:\tZ coordinate to fire at (optional)\r\n"
    +		"\t12:\tSecond X coordinate to fire at (optional; used for slash beams)\r\n"
    +		"\t13:\tSecond Y coordinate to fire at (optional; used for slash beams)\r\n"
    +		"\t14:\tSecond Z coordinate to fire at (optional; used for slash beams)\r\n" },
    +
     	{ OP_IS_TAGGED, "is-tagged\r\n"
     		"\tReturns whether a given ship is tagged or not\r\n"},
     
    Index: code/parse/sexp.h
    ===================================================================
    --- code/parse/sexp.h	(revision 11202)
    +++ code/parse/sexp.h	(working copy)
    @@ -726,6 +726,7 @@
     #define OP_HUD_SET_RETAIL_GAUGE_ACTIVE		(0x0027 | OP_CATEGORY_CHANGE2 | OP_NONCAMPAIGN_FLAG) 	// The E, just revamped a bit by Axem
     #define OP_SCRIPT_EVAL_MULTI				(0x0028 | OP_CATEGORY_CHANGE2 | OP_NONCAMPAIGN_FLAG)	// Karajorma
     #define OP_PAUSE_SOUND_FROM_FILE			(0x0029 | OP_CATEGORY_CHANGE2 | OP_NONCAMPAIGN_FLAG)	// Goober5000
    +#define OP_BEAM_FLOATING_FIRE				(0x002a | OP_CATEGORY_CHANGE2 | OP_NONCAMPAIGN_FLAG)	// MageKing17
     
     // defined for AI goals
     #define OP_AI_CHASE							(0x0000 | OP_CATEGORY_AI | OP_NONCAMPAIGN_FLAG)
    Index: code/weapon/beam.cpp
    ===================================================================
    --- code/weapon/beam.cpp	(revision 11202)
    +++ code/weapon/beam.cpp	(working copy)
    @@ -286,7 +286,7 @@
     {
     	beam *new_item;
     	weapon_info *wip;
    -	ship *firing_ship;
    +	ship *firing_ship = NULL;
     	int objnum;		
     
     	// sanity check
    @@ -311,18 +311,20 @@
     	}
     
     	// make sure the beam_info_index is valid
    -	Assert((fire_info->beam_info_index >= 0) && (fire_info->beam_info_index < MAX_WEAPON_TYPES) && (Weapon_info[fire_info->beam_info_index].wi_flags & WIF_BEAM));
     	if((fire_info->beam_info_index < 0) || (fire_info->beam_info_index >= MAX_WEAPON_TYPES) || !(Weapon_info[fire_info->beam_info_index].wi_flags & WIF_BEAM)){
    +		Assertion(false, "beam_info_index (%d) invalid (either <0 or >= %d or not actually a beam)!\n", fire_info->beam_info_index, MAX_WEAPON_TYPES);
     		return -1;
     	}
     
     	wip = &Weapon_info[fire_info->beam_info_index];	
     	// make sure a ship is firing this
    -	Assert((fire_info->shooter->type == OBJ_SHIP) && (fire_info->shooter->instance >= 0) && (fire_info->shooter->instance < MAX_SHIPS));
    -	if ( (fire_info->shooter->type != OBJ_SHIP) || (fire_info->shooter->instance < 0) || (fire_info->shooter->instance >= MAX_SHIPS) ) {
    +	if (!(fire_info->bfi_flags & BFIF_FLOATING_BEAM) && ((fire_info->shooter->type != OBJ_SHIP) || (fire_info->shooter->instance < 0) || (fire_info->shooter->instance >= MAX_SHIPS)) ) {
    +		Assertion(false, "Non-floating beam not fired from a valid ship!\n");
     		return -1;
     	}
    -	firing_ship = &Ships[fire_info->shooter->instance];
    +	if (fire_info->shooter != NULL) {
    +		firing_ship = &Ships[fire_info->shooter->instance];
    +	}
     
     	// get a free beam
     	new_item = GET_FIRST(&Beam_free_list);
    @@ -352,7 +354,7 @@
     	new_item->warmdown_stamp = -1;
     	new_item->weapon_info_index = fire_info->beam_info_index;	
     	new_item->objp = fire_info->shooter;
    -	new_item->sig = fire_info->shooter->signature;
    +	new_item->sig = (fire_info->shooter != NULL) ? fire_info->shooter->signature : 0;
     	new_item->subsys = fire_info->turret;	
     	new_item->life_left = wip->b_info.beam_life;	
     	new_item->life_total = wip->b_info.beam_life;
    @@ -368,19 +370,22 @@
     	new_item->flags = 0;
     	new_item->shot_index = 0;
     	new_item->shrink = 1.0f;	
    -	new_item->team = (char)firing_ship->team;
    +	new_item->team = (firing_ship == NULL) ? fire_info->team : static_cast<char>(firing_ship->team);
     	new_item->range = wip->b_info.range;
     	new_item->damage_threshold = wip->b_info.damage_threshold;
     	new_item->bank = fire_info->bank;
     	new_item->Beam_muzzle_stamp = -1;
     	new_item->beam_glow_frame = 0.0f;
    -	new_item->firingpoint = fire_info->turret->turret_next_fire_pos;
    +	new_item->firingpoint = (fire_info->bfi_flags & BFIF_FLOATING_BEAM) ? -1 : fire_info->turret->turret_next_fire_pos;
     	new_item->beam_width = wip->b_info.beam_width;
    +	new_item->last_start = fire_info->starting_pos;
     
     	if (fire_info->bfi_flags & BFIF_FORCE_FIRING)
     		new_item->flags |= BF_FORCE_FIRING;
     	if (fire_info->bfi_flags & BFIF_IS_FIGHTER_BEAM)
     		new_item->flags |= BF_IS_FIGHTER_BEAM;
    +	if (fire_info->bfi_flags & BFIF_FLOATING_BEAM)
    +		new_item->flags |= BF_FLOATING_BEAM;
     
     	if (fire_info->bfi_flags & BFIF_TARGETING_COORDS) {
     		new_item->flags |= BF_TARGETING_COORDS;
    @@ -392,7 +397,7 @@
     	}
     
     	for (int i = 0; i < MAX_BEAM_SECTIONS; i++)
    -		new_item->beam_secion_frame[i] = 0.0f;
    +		new_item->beam_section_frame[i] = 0.0f;
     	
     	if (fire_info->bfi_flags & BFIF_IS_FIGHTER_BEAM) {
     		new_item->type = BEAM_TYPE_C;
    @@ -427,7 +432,7 @@
     	}	
     
     	// create the associated object
    -	objnum = obj_create(OBJ_BEAM, OBJ_INDEX(fire_info->shooter), new_item - Beams, &vmd_identity_matrix, &vmd_zero_vector, 1.0f, OF_COLLIDES);
    +	objnum = obj_create(OBJ_BEAM, ((fire_info->shooter != NULL) ? OBJ_INDEX(fire_info->shooter) : -1), new_item - Beams, &vmd_identity_matrix, &vmd_zero_vector, 1.0f, OF_COLLIDES);
     	if(objnum < 0){
     		beam_delete(new_item);
     		nprintf(("General", "obj_create() failed for beam weapon! bah!\n"));
    @@ -590,7 +595,6 @@
     	}
     	b = &Beams[bm->instance];
     
    -	Assert(b->objp != NULL);
     	if(b->objp == NULL){
     		return -1;
     	}
    @@ -734,7 +738,8 @@
     
     	// LEAVE THIS HERE OTHERWISE MUZZLE GLOWS DRAW INCORRECTLY WHEN WARMING UP OR DOWN
     	// get the "originating point" of the beam for this frame. essentially bashes last_start
    -	beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &temp2, (b->flags & BF_IS_FIGHTER_BEAM) > 0);
    +	if (b->subsys != NULL)
    +		beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &temp2, (b->flags & BF_IS_FIGHTER_BEAM) > 0);
     
     	// if the "warming up" timestamp has not expired
     	if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
    @@ -758,7 +763,8 @@
     
     	// LEAVE THIS HERE OTHERWISE MUZZLE GLOWS DRAW INCORRECTLY WHEN WARMING UP OR DOWN
     	// get the "originating point" of the beam for this frame. essentially bashes last_start
    -	beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &temp2, (b->flags & BF_IS_FIGHTER_BEAM) > 0);
    +	if (b->subsys != NULL)
    +		beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &temp2, (b->flags & BF_IS_FIGHTER_BEAM) > 0);
     
     	// if the "warming up" timestamp has not expired
     	if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
    @@ -825,7 +831,8 @@
     
     	// LEAVE THIS HERE OTHERWISE MUZZLE GLOWS DRAW INCORRECTLY WHEN WARMING UP OR DOWN
     	// get the "originating point" of the beam for this frame. essentially bashes last_start
    -	beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &temp2, (b->flags & BF_IS_FIGHTER_BEAM) > 0);
    +	if (b->subsys != NULL)
    +		beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &temp2, (b->flags & BF_IS_FIGHTER_BEAM) > 0);
     
     	// if the "warming up" timestamp has not expired
     	if((b->warmup_stamp != -1) || (b->warmdown_stamp != -1)){
    @@ -879,8 +886,12 @@
     // type e functions
     void beam_type_e_move(beam *b)
     {
    -	vec3d temp, turret_norm;	
    +	vec3d temp, turret_norm;
     
    +	if (b->subsys == NULL) {	// If we're a free-floating beam, there's nothing to calculate here.
    +		return;
    +	}
    +
     	// LEAVE THIS HERE OTHERWISE MUZZLE GLOWS DRAW INCORRECTLY WHEN WARMING UP OR DOWN
     	// get the "originating point" of the beam for this frame. essentially bashes last_start
     	beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &turret_norm, 1, &temp, (b->flags & BF_IS_FIGHTER_BEAM) > 0);
    @@ -911,7 +922,7 @@
     		b = moveup;
     
     		// check if parent object has died, if so then delete beam
    -		if (b->objp->type == OBJ_NONE) {
    +		if (b->objp != NULL && b->objp->type == OBJ_NONE) {
     			// set next beam
     			moveup = GET_NEXT(moveup);
     			// delete current beam
    @@ -925,10 +936,13 @@
     
     		if ( !physics_paused ) {
     			// make sure to check that firingpoint is still properly set
    -			int temp = b->subsys->turret_next_fire_pos;
    +			int temp = -1;
    +			if (b->subsys != NULL) {
    +				temp = b->subsys->turret_next_fire_pos;
     
    -			if (!(b->flags & BF_IS_FIGHTER_BEAM))
    -				b->subsys->turret_next_fire_pos = b->firingpoint;
    +				if (!(b->flags & BF_IS_FIGHTER_BEAM))
    +					b->subsys->turret_next_fire_pos = b->firingpoint;
    +			}
     
     			// move the beam
     			switch (b->type)
    @@ -962,7 +976,9 @@
     				default :
     					Int3();
     			}
    -			b->subsys->turret_next_fire_pos = temp;
    +			if (b->subsys != NULL) {
    +				b->subsys->turret_next_fire_pos = temp;
    +			}
     		}
     
     		// next
    @@ -994,8 +1010,10 @@
     			if(bf_status < 0){
     				beam_delete(moveup);
     			} else {
    -				// add a muzzle light for the shooter
    -				beam_add_light(moveup, OBJ_INDEX(moveup->objp), 0, NULL);
    +				if (moveup->objp != NULL) {
    +					// add a muzzle light for the shooter
    +					beam_add_light(moveup, OBJ_INDEX(moveup->objp), 0, NULL);
    +				}
     
     				// if the warming up timestamp has expired, start firing
     				if(timestamp_elapsed(moveup->warmup_stamp)){							
    @@ -1018,8 +1036,10 @@
     			if(bf_status < 0){
     				beam_delete(moveup);
     			} else {
    -				// add a muzzle light for the shooter
    -				beam_add_light(moveup, OBJ_INDEX(moveup->objp), 0, NULL);
    +				if (moveup->objp != NULL) {
    +					// add a muzzle light for the shooter
    +					beam_add_light(moveup, OBJ_INDEX(moveup->objp), 0, NULL);
    +				}
     
     				// if we're done warming down, the beam is finished
     				if(timestamp_elapsed(moveup->warmdown_stamp)){
    @@ -1033,8 +1053,10 @@
     		}
     		// otherwise, we're firing away.........		
     
    -		// add a muzzle light for the shooter
    -		beam_add_light(moveup, OBJ_INDEX(moveup->objp), 0, NULL);
    +		if (moveup->objp != NULL) {
    +			// add a muzzle light for the shooter
    +			beam_add_light(moveup, OBJ_INDEX(moveup->objp), 0, NULL);
    +		}
     
     		// subtract out the life left for the beam
     		if(!physics_paused){
    @@ -1055,7 +1077,7 @@
     		}		
     
     		// add tube light for the beam
    -		if(Use_GLSL > 1)
    +		if(Use_GLSL > 1 && moveup->objp != NULL)
     			beam_add_light(moveup, OBJ_INDEX(moveup->objp), 1, NULL);
     
     		// stop shooting?
    @@ -1229,18 +1251,18 @@
     		int framenum = 0;
     
     		if (bwsi->texture.num_frames > 1) {
    -			b->beam_secion_frame[s_idx] += flFrametime;
    +			b->beam_section_frame[s_idx] += flFrametime;
     
     			// Sanity checks
    -			if (b->beam_secion_frame[s_idx] < 0.0f)
    -				b->beam_secion_frame[s_idx] = 0.0f;
    -			if (b->beam_secion_frame[s_idx] > 100.0f)
    -				b->beam_secion_frame[s_idx] = 0.0f;
    +			if (b->beam_section_frame[s_idx] < 0.0f)
    +				b->beam_section_frame[s_idx] = 0.0f;
    +			if (b->beam_section_frame[s_idx] > 100.0f)
    +				b->beam_section_frame[s_idx] = 0.0f;
     
    -			while (b->beam_secion_frame[s_idx] > bwsi->texture.total_time)
    -				b->beam_secion_frame[s_idx] -= bwsi->texture.total_time;
    +			while (b->beam_section_frame[s_idx] > bwsi->texture.total_time)
    +				b->beam_section_frame[s_idx] -= bwsi->texture.total_time;
     
    -			framenum = fl2i( (b->beam_secion_frame[s_idx] * bwsi->texture.num_frames) / bwsi->texture.total_time );
    +			framenum = fl2i( (b->beam_section_frame[s_idx] * bwsi->texture.num_frames) / bwsi->texture.total_time );
     
     			CLAMP(framenum, 0, bwsi->texture.num_frames-1);
     		}
    @@ -1298,7 +1320,11 @@
     
     	// get turret info - position and normal
     	turret_pos = b->last_start;
    -	turret_norm = b->subsys->system_info->turret_norm;	
    +	if (b->subsys != NULL) {
    +		turret_norm = b->subsys->system_info->turret_norm;
    +	} else {
    +		vm_vec_normalized_dir(&turret_norm, &b->last_shot, &b->last_start);
    +	}
     
     	// randomly perturb a vector within a cone around the normal
     	vm_vector_2_matrix(&m, &turret_norm, NULL, NULL);
    @@ -1312,7 +1338,9 @@
     		float p_life = frand_range(p_time_ref * 0.5f, p_time_ref * 0.7f);
     		float p_vel = (wip->b_info.beam_muzzle_radius / p_life) * frand_range(0.85f, 1.2f);
     		vm_vec_scale(&particle_dir, -p_vel);
    -		vm_vec_add2(&particle_dir, &b->objp->phys_info.vel);	//move along with our parent
    +		if (b->objp != NULL) {
    +			vm_vec_add2(&particle_dir, &b->objp->phys_info.vel);	//move along with our parent
    +		}
     
     		memset(&pinfo, 0, sizeof(particle_info));
     		pinfo.pos = particle_pos;
    @@ -1933,15 +1961,25 @@
     	beam_weapon_info *bwi;
     	float miss_factor;
     
    -	int temp = b->subsys->turret_next_fire_pos;
    +	if (b->subsys != NULL) {
    +		int temp = b->subsys->turret_next_fire_pos;
     
    -	if (!(b->flags & BF_IS_FIGHTER_BEAM))
    -		b->subsys->turret_next_fire_pos = b->firingpoint;
    +		if (!(b->flags & BF_IS_FIGHTER_BEAM))
    +			b->subsys->turret_next_fire_pos = b->firingpoint;
     
    -	// where the shot is originating from (b->last_start gets filled in)
    -	beam_get_global_turret_gun_info(b->objp, b->subsys, &turret_point, &turret_norm, 1, &p2, (b->flags & BF_IS_FIGHTER_BEAM) > 0);
    +		// where the shot is originating from (b->last_start gets filled in)
    +		beam_get_global_turret_gun_info(b->objp, b->subsys, &turret_point, &turret_norm, 1, &p2, (b->flags & BF_IS_FIGHTER_BEAM) > 0);
     
    -	b->subsys->turret_next_fire_pos = temp;
    +		b->subsys->turret_next_fire_pos = temp;
    +	} else {
    +		turret_point = b->last_start;
    +		if (b->flags & BF_TARGETING_COORDS) {
    +			p2 = b->target_pos1;
    +		} else {
    +			p2 = b->target->pos;
    +		}
    +		vm_vec_normalized_dir(&turret_norm, &p2, &turret_point);
    +	}
     
     	// get a model # to work with
     	model_num = beam_get_model(b->target);
    @@ -2065,23 +2103,27 @@
     		}
     	}
     
    -	int temp_int = b->subsys->turret_next_fire_pos;
    +	if (b->subsys != NULL && b->type != BEAM_TYPE_C) {	// Type C beams don't use this information.
    +		int temp_int = b->subsys->turret_next_fire_pos;
     
    -	if (!(b->flags & BF_IS_FIGHTER_BEAM))
    -		b->subsys->turret_next_fire_pos = b->firingpoint;
    +		if (!(b->flags & BF_IS_FIGHTER_BEAM))
    +			b->subsys->turret_next_fire_pos = b->firingpoint;
     
    -	// setup our initial shot point and aim direction
    -	switch(b->type){
    -	case BEAM_TYPE_A:	
     		// where the shot is originating from (b->last_start gets filled in)
     		beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &p2, (b->flags & BF_IS_FIGHTER_BEAM) > 0);
     
    +		b->subsys->turret_next_fire_pos = temp_int;
    +	}
    +
    +	// setup our initial shot point and aim direction
    +	switch(b->type){
    +	case BEAM_TYPE_A:
     		// if we're targeting a subsystem - shoot directly at it
     		if(b->target_subsys != NULL){
     			vm_vec_unrotate(&b->last_shot, &b->target_subsys->system_info->pnt, &b->target->orient);
    -			vm_vec_add2(&b->last_shot, &b->target->pos);		 
    +			vm_vec_add2(&b->last_shot, &b->target->pos);
     			vm_vec_sub(&temp, &b->last_shot, &b->last_start);
    -			
    +
     			vm_vec_scale_add(&b->last_shot, &b->last_start, &temp, 2.0f);
     			break;
     		}
    @@ -2097,7 +2139,7 @@
     			vm_vec_scale_add(&b->last_shot, &b->last_start, &p2, 2.0f);
     			break;
     		}
    -		
    +
     		// point at the center of the target...
     		if (b->flags & BF_TARGETING_COORDS) {
     			b->last_shot = b->target_pos1;
    @@ -2106,29 +2148,23 @@
     			// ...then jitter based on shot_aim (requires target)
     			beam_jitter_aim(b, b->binfo.shot_aim[0]);
     		}
    -		break;	
    +		break;
     
    -	case BEAM_TYPE_B:		
    -		// where the shot is originating from (b->last_start gets filled in)
    -		beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &p2, (b->flags & BF_IS_FIGHTER_BEAM) > 0);
    -
    +	case BEAM_TYPE_B:
     		// set the shot point
     		vm_vec_scale_add(&b->last_shot, &b->last_start, &b->binfo.dir_a, b->range);
    -		Assert(is_valid_vec(&b->last_shot));		
    +		Assert(is_valid_vec(&b->last_shot));
     		break;
     
     	case BEAM_TYPE_C:
     		// start point
    -		temp = b->targeting_laser_offset;	
    +		temp = b->targeting_laser_offset;
     		vm_vec_unrotate(&b->last_start, &temp, &b->objp->orient);
     		vm_vec_add2(&b->last_start, &b->objp->pos);
    -		vm_vec_scale_add(&b->last_shot, &b->last_start, &b->objp->orient.vec.fvec, b->range);		
    +		vm_vec_scale_add(&b->last_shot, &b->last_start, &b->objp->orient.vec.fvec, b->range);
     		break;
     
    -	case BEAM_TYPE_D:				
    -		// where the shot is originating from (b->last_start gets filled in)
    -		beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &p2, (b->flags & BF_IS_FIGHTER_BEAM) > 0);		
    -		
    +	case BEAM_TYPE_D:
     		// point at the center of the target...
     		if (b->flags & BF_TARGETING_COORDS) {
     			b->last_shot = b->target_pos1;
    @@ -2142,7 +2178,7 @@
     
     	case BEAM_TYPE_E:
     		// where the shot is originating from (b->last_start gets filled in)
    -		beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &p2, (b->flags & BF_IS_FIGHTER_BEAM) > 0);		
    +		beam_get_global_turret_gun_info(b->objp, b->subsys, &b->last_start, &temp, 1, &p2, (b->flags & BF_IS_FIGHTER_BEAM) > 0);
     
     		// point directly in the direction of the turret
     		vm_vec_scale_add(&b->last_shot, &b->last_start, &temp, b->range);
    @@ -2149,11 +2185,9 @@
     		break;
     
     	default:
    -		Int3();
    -	}		
    +		Assertion(false, "Impossible beam type (%d); get a coder!\n", b->type);
    +	}
     
    -	b->subsys->turret_next_fire_pos = temp_int;
    -
     	// recalculate object pairs
     	OBJ_RECALC_PAIRS((&Objects[b->objnum]));
     }
    @@ -2947,6 +2981,14 @@
     			snd_play_3d( &Snds[wi->impact_snd], &b->f_collisions[idx].cinfo.hit_point_world, &Eye_position );
     		}
     
    +		// spawn subweapons as needed
    +		if ( !b->has_hit ) {
    +			b->has_hit = true;
    +			vec3d normal;
    +			vm_vec_unrotate(&normal, &b->f_collisions[idx].cinfo.hit_normal, &Objects[target].orient);
    +			spawn_child_weapons(&Objects[b->objnum], &b->f_collisions[idx].cinfo.hit_point_world, &normal);
    +		}
    +
     		// KOMET_EXT -->
     
     		// draw flash, explosion
    @@ -3236,6 +3278,10 @@
     // if it is legal for the beam to fire, or continue firing
     int beam_ok_to_fire(beam *b)
     {
    +	if (b->objp == NULL) {	// If we don't have a firing object, none of these checks make sense.
    +		return 1;
    +	}
    +
     	// if my own object is invalid, stop firing
     	if (b->objp->signature != b->sig) {
     		mprintf(("BEAM : killing beam because of invalid parent object SIGNATURE!\n"));
    @@ -3265,6 +3311,10 @@
     		}
     	}
     
    +	if (b->subsys == NULL) {	// If we don't have a firing turret, none of these checks make sense.
    +		return 1;
    +	}
    +
     	if (!(b->flags & BF_FORCE_FIRING)) {
     		// if the shooting turret is destroyed	
     		if (b->subsys->current_hits <= 0.0f) {
    Index: code/weapon/beam.h
    ===================================================================
    --- code/weapon/beam.h	(revision 11202)
    +++ code/weapon/beam.h	(working copy)
    @@ -50,6 +50,7 @@
     #define BFIF_IS_FIGHTER_BEAM	(1<<0)
     #define BFIF_FORCE_FIRING		(1<<1)
     #define BFIF_TARGETING_COORDS	(1<<2)
    +#define BFIF_FLOATING_BEAM		(1<<3)
     
     // pass to beam fire 
     typedef struct beam_fire_info {
    @@ -62,11 +63,13 @@
     	ship_subsys		*target_subsys;						// (optional), specific subsystem to be targeted on the target 
     	vec3d			target_pos1;							// if we're shooting off into space
     	vec3d			target_pos2;							// if we're shooting off into space (optional second point)
    +	vec3d			starting_pos;							// starting position for floating beams -MageKing17
     	beam_info		*beam_info_override;			// (optional), pass this in to override all beam movement info (for multiplayer)
     	int				num_shots;						// (optional), only used for type D weapons
     	int bank;									// for fighters, which bank of the primary weapons are they in
     	int point;									// for fighters, which point on the bank it is from
     	int bfi_flags;
    +	char team;									// for floating beams, determines which team the beam is on
     } beam_fire_info;
     
     typedef struct fighter_beam_fire_info {
    @@ -105,6 +108,7 @@
     #define BF_FORCE_FIRING					(1<<2)
     #define BF_IS_FIGHTER_BEAM				(1<<3)
     #define BF_TARGETING_COORDS				(1<<4)
    +#define BF_FLOATING_BEAM				(1<<5)
     
     // beam struct (the actual weapon/object)
     typedef struct beam {
    @@ -139,7 +143,7 @@
     	vec3d	last_start;				
     	int		shot_index;				// for type D beam weapons
     	float	beam_glow_frame;		// what frame a beam glow animation is on
    -	float	beam_secion_frame[MAX_BEAM_SECTIONS];	// what frame a beam secion animation is on
    +	float	beam_section_frame[MAX_BEAM_SECTIONS];	// what frame a beam secion animation is on
     
     	// recent collisions
     	beam_collision r_collisions[MAX_FRAME_COLLISIONS];					// recent collisions
    @@ -167,6 +171,8 @@
     	int firingpoint;
     
     	float		beam_width;
    +
    +	bool	has_hit;	// Set to true after the first time the beam hits something.
     } beam;
     
     extern beam Beams[MAX_BEAMS];				// all beams
    Index: code/weapon/weapon.h
    ===================================================================
    --- code/weapon/weapon.h	(revision 11202)
    +++ code/weapon/weapon.h	(working copy)
    @@ -631,7 +631,7 @@
     bool weapon_armed(weapon *wp, bool hit_target);
     void weapon_hit( object * weapon_obj, object * other_obj, vec3d * hitpos, int quadrant = -1 );
     int cmeasure_name_lookup(char *name);
    -void spawn_child_weapons( object *objp );
    +void spawn_child_weapons( object *objp, vec3d *override_pos = NULL, vec3d *override_fvec = NULL );
     
     // call to detonate a weapon. essentially calls weapon_hit() with other_obj as NULL, and sends a packet in multiplayer
     void weapon_detonate(object *objp);
    @@ -670,4 +670,7 @@
     // Unpauses all running weapon sounds
     void weapon_unpause_sounds();
     
    +// Called by hudartillery.cpp after SSMs have been parsed to make sure that $SSM: entries defined in weapons are valid.
    +void validate_SSM_entries();
    +
     #endif
    Index: code/weapon/weapons.cpp
    ===================================================================
    --- code/weapon/weapons.cpp	(revision 11202)
    +++ code/weapon/weapons.cpp	(working copy)
    @@ -48,8 +48,26 @@
     #include "stats/scoring.h"
     #include "mod_table/mod_table.h"
     #include "debugconsole/console.h"
    +#include "hud/hudartillery.h"
     
     
    +// Since SSMs are parsed after weapons, if we want to allow SSM strikes to be specified by name, we need to store those names until after SSMs are parsed.
    +typedef struct delayed_ssm_data {
    +	SCP_string filename;
    +	int linenum;
    +	SCP_string ssm_entry;
    +} delayed_ssm_data;
    +SCP_map<SCP_string, delayed_ssm_data> Delayed_SSM_data;
    +SCP_vector<SCP_string> Delayed_SSM_names;
    +
    +typedef struct delayed_ssm_index_data {
    +	SCP_string filename;
    +	int linenum;
    +} delayed_ssm_index_data;
    +SCP_map<SCP_string, delayed_ssm_index_data> Delayed_SSM_indices_data;
    +SCP_vector<SCP_string> Delayed_SSM_indices;
    +
    +
     #ifndef NDEBUG
     int Weapon_flyby_sound_enabled = 1;
     DCF_BOOL( weapon_flyby, Weapon_flyby_sound_enabled )
    @@ -1082,7 +1100,7 @@
     // return 0 if successful, otherwise return -1
     #define WEAPONS_MULTITEXT_LENGTH 2048
     
    -int parse_weapon(int subtype, bool replace)
    +int parse_weapon(int subtype, bool replace, const char *filename)
     {
     	char buf[WEAPONS_MULTITEXT_LENGTH];
     	weapon_info *wip = NULL;
    @@ -2508,7 +2526,25 @@
     	}	
     
     	if( optional_string("$SSM:")){
    -		stuff_int(&wip->SSM_index);
    +		if (stuff_int_optional(&wip->SSM_index) != 2) {
    +			// We can't make an SSM lookup yet, because weapons are parsed first, but we can save the data to process later. -MageKing17
    +			stuff_string(fname, F_NAME, NAME_LENGTH);
    +			delayed_ssm_data temp_data;
    +			temp_data.filename = filename;
    +			temp_data.linenum = get_line_num();
    +			temp_data.ssm_entry = fname;
    +			if (Delayed_SSM_data.find(wip->name) == Delayed_SSM_data.end())
    +				Delayed_SSM_names.push_back(wip->name);
    +			Delayed_SSM_data[wip->name] = temp_data;
    +		} else {
    +			// We'll still want to validate the index later. -MageKing17
    +			delayed_ssm_index_data temp_data;
    +			temp_data.filename = filename;
    +			temp_data.linenum = get_line_num();
    +			if (Delayed_SSM_indices_data.find(wip->name) == Delayed_SSM_indices_data.end())
    +				Delayed_SSM_indices.push_back(wip->name);
    +			Delayed_SSM_indices_data[wip->name] = temp_data;
    +		}
     	}// SSM index -Bobboau
     
     	if(optional_string("$FOF:")){
    @@ -2797,7 +2833,7 @@
     	{
     		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 ) {
    +			if ( parse_weapon(WP_LASER, Parsing_modular_table, filename) < 0 ) {
     				continue;
     			}
     		}
    @@ -2808,7 +2844,7 @@
     	{
     		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) {
    +			if ( parse_weapon(WP_MISSILE, Parsing_modular_table, filename) < 0) {
     				continue;
     			}
     		}
    @@ -2819,7 +2855,7 @@
     	{
     		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) {
    +			if ( parse_weapon(WP_BEAM, Parsing_modular_table, filename) < 0) {
     				continue;
     			}
     		}
    @@ -2830,7 +2866,7 @@
     	{
     		while (required_string_either("#End", "$Name:"))
     		{
    -			int idx = parse_weapon(WP_MISSILE, Parsing_modular_table);
    +			int idx = parse_weapon(WP_MISSILE, Parsing_modular_table, filename);
     
     			if(idx < 0) {
     				continue;
    @@ -5444,21 +5480,33 @@
     /**
      * Spawn child weapons from object *objp.
      */
    -void spawn_child_weapons(object *objp)
    +void spawn_child_weapons(object *objp, vec3d *override_pos, vec3d *override_fvec)
     {
     	int	i, j;
     	int	child_id;
     	int	parent_num;
     	ushort starting_sig;
    -	weapon	*wp;
    +	weapon	*wp = NULL;
    +	beam	*bp = NULL;
     	weapon_info	*wip, *child_wip;
    +	vec3d	*opos, *fvec;
     
    -	Assert(objp->type == OBJ_WEAPON);
    -	Assert((objp->instance >= 0) && (objp->instance < MAX_WEAPONS));
    +	Assertion(objp->type == OBJ_WEAPON || objp->type == OBJ_BEAM, "spawn_child_weapons() doesn't make sense for non-weapon non-beam objects; get a coder!\n");
    +	Assertion(objp->instance >= 0, "spawn_child_weapons() called with an object with an instance of %d; get a coder!\n", objp->instance);
    +	Assertion(!(objp->type == OBJ_WEAPON) || (objp->instance < MAX_WEAPONS), "spawn_child_weapons() called with a weapon with an instance of %d while MAX_WEAPONS is %d; get a coder!\n", objp->instance, MAX_WEAPONS);
    +	Assertion(!(objp->type == OBJ_BEAM) || (objp->instance < MAX_BEAMS), "spawn_child_weapons() called with a beam with an instance of %d while MAX_BEAMS is %d; get a coder!\n", objp->instance, MAX_BEAMS);
     
    -	wp = &Weapons[objp->instance];
    -	Assert((wp->weapon_info_index >= 0) && (wp->weapon_info_index < MAX_WEAPON_TYPES));
    -	wip = &Weapon_info[wp->weapon_info_index];
    +	if (objp->type == OBJ_WEAPON) {
    +		wp = &Weapons[objp->instance];
    +		Assertion((wp->weapon_info_index >= 0) && (wp->weapon_info_index < MAX_WEAPON_TYPES), "Invalid weapon_info_index of %d; get a coder!\n", wp->weapon_info_index);
    +		wip = &Weapon_info[wp->weapon_info_index];
    +	} else if (objp->type == OBJ_BEAM) {
    +		bp = &Beams[objp->instance];
    +		Assertion((bp->weapon_info_index >= 0) && (bp->weapon_info_index < MAX_WEAPON_TYPES), "Invalid weapon_info_index of %d; get a coder!\n", bp->weapon_info_index);
    +		wip = &Weapon_info[bp->weapon_info_index];
    +	} else {	// Let's make sure we don't do screwball things in a release build if this gets called with a non-weapon non-beam.
    +		return;
    +	}
     
     	parent_num = objp->parent;
     
    @@ -5479,56 +5527,87 @@
     		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;
    +	if (override_pos != NULL) {
    +		opos = override_pos;
    +	} else {
    +		opos = &objp->pos;
    +	}
    +	if (override_fvec != NULL) {
    +		fvec = override_fvec;
    +	} else {
    +		fvec = &objp->orient.vec.fvec;
    +	}
     
    -            child_id = wip->spawn_info[i].spawn_type;
    +	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);
    +			// for multiplayer, use the static randvec functions based on the network signatures to provide
    +			// the randomness so that it is the same on all machines.
    +			if ( Game_mode & GM_MULTIPLAYER ) {
    +				static_rand_cone(objp->net_signature + j, &tvec, fvec, wip->spawn_info[i].spawn_angle);
    +			} else {
    +				vm_vec_random_cone(&tvec, fvec, wip->spawn_info[i].spawn_angle);
    +			}
    +			vm_vec_scale_add(&pos, opos, &tvec, objp->radius);
     
    -		    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);
    +			// Let's allow beam-spawn! -MageKing17
    +			if (child_wip->wi_flags & WIF_BEAM) {
    +				beam_fire_info fire_info;
    +				memset(&fire_info, 0, sizeof(beam_fire_info));
     
    -			//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];
    +				fire_info.accuracy = 0.000001f;		// this will guarantee a hit
    +				fire_info.shooter = &Objects[parent_num];
    +				fire_info.turret = NULL;
    +				fire_info.target = NULL;
    +				fire_info.target_subsys = NULL;
    +				fire_info.target_pos1 = fire_info.target_pos2 = pos;
    +				fire_info.bfi_flags |= BFIF_FLOATING_BEAM | BFIF_TARGETING_COORDS;
    +				fire_info.starting_pos = objp->pos;
    +				fire_info.beam_info_index = child_id;
    +				fire_info.team = static_cast<char>(obj_team(&Objects[parent_num]));
     
    -					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);
    +				// fire the beam
    +				beam_fire(&fire_info);
    +			} else {
    +				vm_vector_2_matrix(&orient, &tvec, NULL, NULL);
    +				weapon_objnum = weapon_create(&pos, &orient, child_id, parent_num, -1, wp->weapon_flags & WF_LOCKED_WHEN_FIRED, 1);
    +
    +				//if the child inherits parent target, do it only if the parent weapon was locked to begin with
    +				if ((child_wip->wi_flags2 & WIF2_INHERIT_PARENT_TARGET) && (wp->homing_object != &obj_used_list))
    +				{
    +					//Deal with swarm weapons
    +					if (wp->swarm_index >= 0) {
    +						swarm_info	*swarmp;
    +						swarmp = &Swarm_missiles[wp->swarm_index];
    +
    +						weapon_set_tracking_info(weapon_objnum, parent_num, swarmp->homing_objnum, 1, wp->homing_subsys);
    +					} else {
    +						weapon_set_tracking_info(weapon_objnum, parent_num, wp->target_num, 1, wp->homing_subsys);
    +					}
     				}
    -			}
     
    -    		//	Assign a little randomness to lifeleft so they don't all disappear at the same time.
    -		    if (weapon_objnum != -1) {
    -			    float rand_val;
    +				//	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);
    -			    }
    +					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;
    -		    }
    -        }
    +					Weapons[Objects[weapon_objnum].instance].lifeleft *= rand_val*0.4f + 0.8f;
    +				}
    +			}
    +		}
     
     	}
     
    @@ -6991,3 +7070,43 @@
     	// Pause all beam sounds
     	beam_unpause_sounds();
     }
    +
    +// Called by hudartillery.cpp after SSMs have been parsed to make sure that $SSM: entries defined in weapons are valid.
    +void validate_SSM_entries()
    +{
    +	int wi;
    +	SCP_vector<SCP_string>::const_iterator it;
    +	weapon_info *wip;
    +
    +	for (it = Delayed_SSM_names.begin(); it != Delayed_SSM_names.end(); ++it) {
    +		delayed_ssm_data *dat = &Delayed_SSM_data[*it];
    +		wi = weapon_info_lookup(it->c_str());
    +		Assertion(wi >= 0, "Trying to validate non-existant weapon '%s'; get a coder!\n", it->c_str());
    +		wip = &Weapon_info[wi];
    +		nprintf(("parse", "Starting validation of '%s' [wip->name is '%s'], currently has an SSM_index of %d.\n", it->c_str(), wip->name, wip->SSM_index));
    +		wip->SSM_index = ssm_info_lookup(dat->ssm_entry.c_str());
    +		if (wip->SSM_index < 0) {
    +			Warning(LOCATION, "Unknown SSM entry '%s' in specification for %s (%s:line %d).\n", dat->ssm_entry.c_str(), it->c_str(), dat->filename.c_str(), dat->linenum);
    +		}
    +		nprintf(("parse", "Validation complete, SSM_index is %d.\n", wip->SSM_index));
    +	}
    +
    +	// This information is no longer relevant, so might as well clear it out.
    +	Delayed_SSM_data.clear();
    +	Delayed_SSM_names.clear();
    +
    +	for (it = Delayed_SSM_indices.begin(); it != Delayed_SSM_indices.end(); ++it) {
    +		delayed_ssm_index_data *dat = &Delayed_SSM_indices_data[*it];
    +		wi = weapon_info_lookup(it->c_str());
    +		Assertion(wi >= 0, "Trying to validate non-existant weapon '%s'; get a coder!\n", it->c_str());
    +		wip = &Weapon_info[wi];
    +		nprintf(("parse", "Starting validation of '%s' [wip->name is '%s'], currently has an SSM_index of %d.\n", it->c_str(), wip->name, wip->SSM_index));
    +		if (wip->SSM_index < -1 || wip->SSM_index >= static_cast<int>(Ssm_info.size())) {
    +			Warning(LOCATION, "Invalid SSM index '%d' (should be 0-%d) in specification for %s (%s:line %d).\n", wip->SSM_index, Ssm_info.size()-1, it->c_str(), dat->filename.c_str(), dat->linenum);
    +		}
    +		nprintf(("parse", "Validation complete, SSM_index is %d.\n", wip->SSM_index));
    +	}
    +
    +	Delayed_SSM_indices_data.clear();
    +	Delayed_SSM_indices.clear();
    +}
    
    patch file icon subspace_beams.patch (59,568 bytes) 2014-12-22 23:12 +

-Relationships
+Relationships

-Notes

~0016268

MageKing17 (developer)

The very basic tables and mission I used for testing have been attached. Not included: a test of beam-create, because I didn't test it with this mission, and the two-second FRED-job I used to make sure it worked wasn't worth keeping.

~0016269

MageKing17 (developer)

Patch file updated to correct an issue where sound_index wasn't being initialized to -1, so depending on what uninitialized data you got, FSO would tend to crash whenever an SSM strike was triggered.

~0016288

niffiwan (developer)

Points 3-5 have been committed to trunk in r11078.

~0016338

MageKing17 (developer)

Last edited: 2014-10-17 16:54

View 2 revisions

Patch updated; I think the only new feature here is that "+Spherical" has been changed to "+Shape: Sphere" for extensibility purposes (default behavior is "+Shape: Circle", and I also added a somewhat-pointless "+Shape: Point" for... no reason at all, really).

I'm also updating the example data files to the current versions (both so that FSO won't complain about "+Spherical" and so that people can play with the TAG-CEPTION).

EDIT: Oh, I also changed it so that the point during the warp effect when the weapon fires is only calculated once, instead of every frame until it fires; the original implementation just seemed so sloppy that way.

~0016366

MageKing17 (developer)

Updated again; removed MAX_SSM_COUNT, so SSM strikes can now have an arbitrary number of projectiles. Re-added the TAG-K to demonstrate with a 200-strong SSKayser barrage (which runs up against the MAX_FIREBALLS limit).

Also made "+Count:" optional (defaulting to 1), because it just seems polite to make as many lines optional as possible.

~0016432

MageKing17 (developer)

Patch updated with beam support for "spawn". The simple case (projectile spawns floating beam(s) when it dies) is tested, but the code attempts to also allow beams to themselves spawn other weapons. This code is both untested and unfinished; it won't be finished until the tabler can make some specification of how the base angle from which the subweapons should spawn is determined (to allow, for instance, reflection or auto-targeting of nearby hostiles).

~0016658

MageKing17 (developer)

This needs some additional work before it's actually ready for code review.

~0016662

Goober5000 (administrator)

Tweaking target.

~0016706

MageKing17 (developer)

Pull request: https://github.com/scp-fs2open/fs2open.github.com/pull/104

~0016740

MageKing17 (developer)

Pull request has been merged; if I take another stab at beams spawning weapons, I'll make a separate issue for it.
+Notes

-Issue History
Date Modified Username Field Change
2014-09-04 23:40 MageKing17 New Issue
2014-09-04 23:40 MageKing17 File Added: subspace_beams.patch
2014-09-04 23:40 MageKing17 Assigned To => MageKing17
2014-09-04 23:40 MageKing17 Status new => code review
2014-09-04 23:48 MageKing17 File Added: test-ssm.tbm
2014-09-04 23:48 MageKing17 File Added: test-wep.tbm
2014-09-04 23:48 MageKing17 File Added: test-shp.tbm
2014-09-04 23:48 MageKing17 File Added: TAGTest.fs2
2014-09-04 23:50 MageKing17 Note Added: 0016268
2014-09-05 21:51 MageKing17 File Deleted: subspace_beams.patch
2014-09-05 21:51 MageKing17 File Added: subspace_beams.patch
2014-09-05 21:53 MageKing17 Note Added: 0016269
2014-09-22 05:29 niffiwan Note Added: 0016288
2014-10-17 16:45 MageKing17 File Deleted: subspace_beams.patch
2014-10-17 16:45 MageKing17 File Added: subspace_beams.patch
2014-10-17 16:50 MageKing17 Note Added: 0016338
2014-10-17 16:50 MageKing17 File Deleted: TAGTest.fs2
2014-10-17 16:50 MageKing17 File Deleted: test-shp.tbm
2014-10-17 16:50 MageKing17 File Deleted: test-wep.tbm
2014-10-17 16:50 MageKing17 File Deleted: test-ssm.tbm
2014-10-17 16:50 MageKing17 File Added: test-ssm.tbm
2014-10-17 16:50 MageKing17 File Added: test-wep.tbm
2014-10-17 16:50 MageKing17 File Added: test-shp.tbm
2014-10-17 16:50 MageKing17 File Added: TAGTest.fs2
2014-10-17 16:52 MageKing17 Description Updated View Revisions
2014-10-17 16:54 MageKing17 Note Edited: 0016338 View Revisions
2014-10-17 16:58 MageKing17 Description Updated View Revisions
2014-10-17 17:06 MageKing17 Additional Information Updated View Revisions
2014-10-31 17:49 MageKing17 File Deleted: subspace_beams.patch
2014-10-31 17:49 MageKing17 File Deleted: test-ssm.tbm
2014-10-31 17:49 MageKing17 File Deleted: test-wep.tbm
2014-10-31 17:50 MageKing17 File Deleted: test-shp.tbm
2014-10-31 17:50 MageKing17 File Deleted: TAGTest.fs2
2014-10-31 17:50 MageKing17 File Added: subspace_beams.patch
2014-10-31 17:50 MageKing17 File Added: test-shp.tbm
2014-10-31 17:50 MageKing17 File Added: test-ssm.tbm
2014-10-31 17:50 MageKing17 File Added: test-wep.tbm
2014-10-31 17:50 MageKing17 File Added: TAGTest.fs2
2014-10-31 17:53 MageKing17 Note Added: 0016366
2014-12-22 23:12 MageKing17 File Deleted: subspace_beams.patch
2014-12-22 23:12 MageKing17 File Added: subspace_beams.patch
2014-12-22 23:24 MageKing17 Note Added: 0016432
2015-04-23 17:37 MageKing17 Note Added: 0016658
2015-04-23 17:37 MageKing17 Status code review => assigned
2015-04-23 23:08 Goober5000 Note Added: 0016662
2015-04-23 23:08 Goober5000 Target Version 3.7.3 => 3.7.4
2015-05-15 19:06 MageKing17 Note Added: 0016706
2015-05-15 19:06 MageKing17 Status assigned => code review
2015-05-28 17:33 MageKing17 Note Added: 0016740
2015-05-28 17:33 MageKing17 Status code review => resolved
2015-05-28 17:33 MageKing17 Resolution open => fixed
2015-05-28 17:33 MageKing17 Fixed in Version => 3.7.3
+Issue History