View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0003103 | FSSCP | tables | public | 2014-09-05 03:40 | 2015-05-28 21:33 |
Reporter | MageKing17 | Assigned To | MageKing17 | ||
Priority | normal | Severity | feature | Reproducibility | N/A |
Status | resolved | Resolution | fixed | ||
Target Version | 3.7.4 | Fixed in Version | 3.7.3 | ||
Summary | 0003103: Modular ssm.tbl, floating beams, and subspace beam strikes | ||||
Description | This 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 Information | Test build here: http://deviance.duckish.net/downloads/fs2_fred2_open_ssb_test.7z | ||||
Tags | No tags attached. | ||||
|
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. |
|
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. |
|
Points 3-5 have been committed to trunk in r11078. |
|
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. |
|
|
|
|
|
|
|
|
|
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. |
|
subspace_beams.patch (59,568 bytes)
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 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). |
|
This needs some additional work before it's actually ready for code review. |
|
Tweaking target. |
|
Pull request: https://github.com/scp-fs2open/fs2open.github.com/pull/104 |
|
Pull request has been merged; if I take another stab at beams spawning weapons, I'll make a separate issue for it. |
Date Modified | Username | Field | Change |
---|---|---|---|
2014-09-05 03:40 | MageKing17 | New Issue | |
2014-09-05 03:40 | MageKing17 | File Added: subspace_beams.patch | |
2014-09-05 03:40 | MageKing17 | Assigned To | => MageKing17 |
2014-09-05 03:40 | MageKing17 | Status | new => code review |
2014-09-05 03:48 | MageKing17 | File Added: test-ssm.tbm | |
2014-09-05 03:48 | MageKing17 | File Added: test-wep.tbm | |
2014-09-05 03:48 | MageKing17 | File Added: test-shp.tbm | |
2014-09-05 03:48 | MageKing17 | File Added: TAGTest.fs2 | |
2014-09-05 03:50 | MageKing17 | Note Added: 0016268 | |
2014-09-06 01:51 | MageKing17 | File Deleted: subspace_beams.patch | |
2014-09-06 01:51 | MageKing17 | File Added: subspace_beams.patch | |
2014-09-06 01:53 | MageKing17 | Note Added: 0016269 | |
2014-09-22 09:29 | niffiwan | Note Added: 0016288 | |
2014-10-17 20:45 | MageKing17 | File Deleted: subspace_beams.patch | |
2014-10-17 20:45 | MageKing17 | File Added: subspace_beams.patch | |
2014-10-17 20:50 | MageKing17 | Note Added: 0016338 | |
2014-10-17 20:50 | MageKing17 | File Deleted: TAGTest.fs2 | |
2014-10-17 20:50 | MageKing17 | File Deleted: test-shp.tbm | |
2014-10-17 20:50 | MageKing17 | File Deleted: test-wep.tbm | |
2014-10-17 20:50 | MageKing17 | File Deleted: test-ssm.tbm | |
2014-10-17 20:50 | MageKing17 | File Added: test-ssm.tbm | |
2014-10-17 20:50 | MageKing17 | File Added: test-wep.tbm | |
2014-10-17 20:50 | MageKing17 | File Added: test-shp.tbm | |
2014-10-17 20:50 | MageKing17 | File Added: TAGTest.fs2 | |
2014-10-17 20:52 | MageKing17 | Description Updated | |
2014-10-17 20:54 | MageKing17 | Note Edited: 0016338 | |
2014-10-17 20:58 | MageKing17 | Description Updated | |
2014-10-17 21:06 | MageKing17 | Additional Information Updated | |
2014-10-31 21:49 | MageKing17 | File Deleted: subspace_beams.patch | |
2014-10-31 21:49 | MageKing17 | File Deleted: test-ssm.tbm | |
2014-10-31 21:49 | MageKing17 | File Deleted: test-wep.tbm | |
2014-10-31 21:50 | MageKing17 | File Deleted: test-shp.tbm | |
2014-10-31 21:50 | MageKing17 | File Deleted: TAGTest.fs2 | |
2014-10-31 21:50 | MageKing17 | File Added: subspace_beams.patch | |
2014-10-31 21:50 | MageKing17 | File Added: test-shp.tbm | |
2014-10-31 21:50 | MageKing17 | File Added: test-ssm.tbm | |
2014-10-31 21:50 | MageKing17 | File Added: test-wep.tbm | |
2014-10-31 21:50 | MageKing17 | File Added: TAGTest.fs2 | |
2014-10-31 21:53 | MageKing17 | Note Added: 0016366 | |
2014-12-23 04:12 | MageKing17 | File Deleted: subspace_beams.patch | |
2014-12-23 04:12 | MageKing17 | File Added: subspace_beams.patch | |
2014-12-23 04:24 | MageKing17 | Note Added: 0016432 | |
2015-04-23 21:37 | MageKing17 | Note Added: 0016658 | |
2015-04-23 21:37 | MageKing17 | Status | code review => assigned |
2015-04-24 03:08 | Goober5000 | Note Added: 0016662 | |
2015-04-24 03:08 | Goober5000 | Target Version | 3.7.3 => 3.7.4 |
2015-05-15 23:06 | MageKing17 | Note Added: 0016706 | |
2015-05-15 23:06 | MageKing17 | Status | assigned => code review |
2015-05-28 21:33 | MageKing17 | Note Added: 0016740 | |
2015-05-28 21:33 | MageKing17 | Status | code review => resolved |
2015-05-28 21:33 | MageKing17 | Resolution | open => fixed |
2015-05-28 21:33 | MageKing17 | Fixed in Version | => 3.7.3 |